-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaq-graphics.aq
1 lines (1 loc) · 67.7 KB
/
aq-graphics.aq
1
{"config":{"title":"Aquarium SVG Graphics Library","description":"A library that displays SVG graphics in protocols","copyright":"University of Washington","version":"0.0.1","authors":[{"name":"Justin Vrana","affiliation":"University of Washington"}],"maintainer":{"name":"Justin Vrana","email":"[email protected]"},"acknowledgements":null,"github":{"user":"jvrana","repo":"aq-graphics","organization":"klavinslab"},"keywords":null,"aquadoc_version":"1.0.2","aquarium_version":"\u003c%= Bioturk::Application.config.aquarium_version %\u003e"},"components":[{"library":{"name":"GraphicsBase","category":"SVGGraphics","code_source":"############################################\n#\n# Generic SVG Graphics for lab work\n#\n############################################\n\nrequire 'matrix'\n\nmodule Graphics\n\n class Tag\n # an HTML/XML like tag\n #\n def initialize(tag_name, value: nil, properties: nil)\n if properties.nil?\n properties = {}\n end\n if value.nil?\n value = \"\"\n end\n @tag_name = tag_name\n @properties = properties\n @value = value\n end\n\n def Tag.property(label, value)\n return \"#{label}=\\\"#{value}\\\"\"\n end\n\n def value\n @value\n end\n\n def update(props)\n @properties = @properties.merge(props)\n end\n\n def properties\n @properties.select {|_, v|\n !v.nil? and v != \"\"}.map {|k, v|\n Tag.property(k, v)\n }.join(' ')\n end\n\n def formatter\n REXML::Formatters::Pretty.new\n end\n\n def to_str\n props = self.properties\n if props != \"\"\n props = \" \" + props\n end\n if self.value == \"\"\n return \"\u003c#{@tag_name}#{props} /\u003e\"\n end\n\n mystr = \"\u003c#{@tag_name}#{props}\u003e#{self.value}\u003c/#{@tag_name}\u003e\"\n return mystr\n mydoc = REXML::Document.new(mystr)\n self.formatter.write(mydoc.root, \"\")\n end\n\n def dump\n {\n properties: @properties,\n value: @value,\n tag_name: @tag_name\n }\n end\n\n def self.load(props)\n print(\"loading #{self}\")\n return self.new(props[:tag_name], value: props[:value], properties: props[:properties])\n end\n\n def inst\n self.class.load(self.dump)\n end\n\n # def inst\n # Marshal.load(Marshal.dump(self))\n # end\n\n def to_s\n self.to_str\n end\n end\n\n # A vector image, like an icon\n class SVGElement \u003c Tag\n attr_accessor :alignment, :value, :boundx, :boundy\n attr_reader :x, :y, :yscale, :rot, :xposrot, :yposrot, :xscale, :yscale, :name, :classname, :transformations\n\n @@debug = false\n\n # A SVG element to display in an image\n #\n def initialize(children: nil,\n name: nil,\n classname: nil,\n x: 0,\n y: 0,\n boundx: 0,\n boundy: 0,\n alignment: 'top-left',\n xscale: 1,\n yscale: 1,\n rot: 0,\n xposrot: 0,\n yposrot: 0,\n properties: nil,\n transformations: nil,\n style: nil)\n properties = properties || {}\n classname = classname || \"\"\n children = [children] unless children.is_a?(Array)\n super(\"g\", properties: properties)\n @name = name\n @classname = classname\n @boundx = boundx\n @boundy = boundy\n @children = children\n @alignment = alignment\n @style = style || \"\"\n @transformations = transformations || []\n self.update_coordinates!(x, y)\n @xscale = xscale\n @yscale = yscale\n if @transformations.empty?\n self.transform!(x: x, y: y, xscale: xscale, yscale: yscale, rot: rot, xposrot: xposrot, yposrot: yposrot)\n end\n end\n\n def update_coordinates!(x, y)\n @x = x\n @y = y\n end\n\n def dump\n tag_props = super\n tag_props.reject! {|k, v| [\"tag_name\", \"value\"].include?(k.to_s)}\n props = {\n x: @x,\n y: @y,\n xscale: @xscale,\n yscale: @yscale,\n name: @name,\n classname: @classname,\n alignment: @alignment,\n style: @style,\n transformations: @transformations.dup.compact,\n children: @children.dup.compact,\n boundx: @boundx,\n boundy: @boundy\n }\n props.merge(tag_props)\n end\n\n def self.load(props)\n self.new(**props)\n end\n\n # sets the debug mode on or off\n # debug mode will display bounding boxes and anchors\n def self.debug onoroff\n @@debug = onoroff\n end\n\n def value\n # override the Tag value for converting SVGElement to a string\n @value = \"\\n#{self.display}\"\n super\n end\n\n def new_class(myclass)\n inst = self.inst\n inst.new_class!(myclass)\n end\n\n def new_name(myname)\n inst = self.inst\n inst.new_name!(myname)\n end\n\n def new_class!(myclass)\n @classname = myclass\n self\n end\n\n def new_name!(myname)\n @name = myname\n self\n end\n\n def svg_properties\n # additional svg properties\n props = {}\n if @name != \"\"\n props[\"id\"] = @name\n end\n if @classname != \"\"\n props[\"class\"] = @classname\n end\n props[\"transform\"] = self.get_transform_attribute\n props\n end\n\n def properties\n # override the Tab properties to add additional svg-specific properties\n # such as id=\"\" and transform\n @properties = @properties.merge(self.svg_properties)\n super\n end\n\n def origin\n self.get_anchor(\"upper-left\")\n end\n\n def bounds\n self.bounds_helper(0, 0)\n # use min?\n end\n\n def align_with(other, other_anchor)\n self.translate!(-@x, -@y)\n v = other.get_abs_anchor(other_anchor)\n v = v - other.abs_anchor_vector\n # v = other.get_abs_anchor_vector(other_anchor)\n #\n self.translate!(*v)\n end\n\n def g id: nil, classname: nil\n # return a external new group with svg element as a child\n new_g = SVGElement.new(name: id, properties: {\"class\" =\u003e classname}, x: 0, y: 0)\n new_g.update_coordinates!(@x, @y)\n new_g.boundx = @boundx * @xscale\n new_g.boundy = @boundy * @yscale\n new_g.add_child(self)\n end\n\n # Sets the children array\n def children=(g)\n @children = g\n end\n\n # Returns the children array\n def children\n @children\n end\n\n def display\n inst = self\n if @@debug\n inst = inst.display_with_anchors.bb\n end\n if inst.children.is_a?(Array)\n inst.children.map do |child|\n if child.is_a?(SVGElement)\n \"#{child}\"\n else\n child\n end\n end.join(\"\\n\")\n else\n \"#{inst.children}\"\n end\n end\n\n def add_child(child)\n if child.is_a?(String)\n @children.push(child)\n else\n @children.push(child.inst)\n end\n self\n end\n\n def group_children id: nil, classname: nil\n # make an internal grouping of this elements children. Return that grouping\n child_group = SVGElement.new(name: id, classname: classname)\n child_group.children = self.children\n self.children = [child_group]\n return child_group\n end\n\n def cx\n return (@x + @boundx) / 2.0\n end\n\n def cy\n return (@y + @boundy) / 2.0\n end\n\n # ########################################################\n # Transformation\n # ########################################################\n\n # scaling always happens last in SVG transform attribute to avoid unusual SVG behavior\n def get_transform_attribute\n # transformations = [self.apply_scale, self.apply_translate, self.apply_rotate]\n transformations = []\n transformations += @transformations.dup\n transformations.push(\"scale(#{@xscale} #{@yscale})\")\n transformations.push(self.anchor_translate)\n attr = transformations.join(' ')\n zero = \"(-){0,1}0(\\.0){0,1}\"\n attr.gsub!(/translate\\((-){0,1}0(\\.0){0,1} (-){0,1}0(\\.0){0,1}\\)/, '')\n attr.gsub!(/translate\\((-){0,1}0(\\.0){0,1}\\)/, '')\n attr.gsub!(/rotate\\((-){0,1}0(\\.0){0,1} (-){0,1}0(\\.0){0,1} (-){0,1}0(\\.0){0,1}\\)\\s+/, \"\")\n attr.gsub!(/scale\\(1\\)\\s+/, \"\")\n attr.gsub!(/scale\\(1 1\\)\\s+/, \"\")\n attr.strip.gsub(/\\s+/, \" \")\n end\n\n def align!(alignment)\n self.alignment = alignment\n self\n end\n\n def mirror_horizontal\n child_group = self.group_children(id: \"mirror_horizontal\")\n child_group.scale!(-1, 1).translate!(@boundx, 0)\n return self\n end\n\n def mirror_vertical\n child_group = self.group_children(id: \"mirror_vertical\")\n child_group.scale!(1, -1).translate!(0, @boundy)\n return self\n end\n\n def translate!(x, y = 0)\n @x += x\n y = y || 0\n @y += y\n if x != 0 or y != 0\n @transformations.push self.translate_helper(x, y)\n end\n self\n end\n\n def rotate!(a, x = 0, y = 0)\n if a != 0\n @transformations.push self.rotate_helper(a, x, y)\n end\n self\n end\n\n # Scalings must always happen last to avoid unusual SVG behaviors\n def scale!(x, y = nil)\n if y.nil?\n y = x\n end\n @xscale = x\n @yscale = y\n # if x != 1 and y != 1\n # @transformations.push self.scale_helper(x, y)\n # end\n self\n end\n\n def transform!(x: 0, y: 0, xscale: 1, yscale: 1, rot: 0, xposrot: 0, yposrot: 0)\n # update the tranform values\n self.translate!(x, y)\n .rotate!(rot, xposrot, yposrot)\n .scale!(xscale, yscale)\n end\n\n def translate(x, y = nil)\n i = self.inst\n i.translate!(x, y)\n end\n\n def rotate(a, x = 0, y = 0)\n i = self.inst\n i.rotate!(a, x, y)\n end\n\n def scale(x, y = nil)\n i = self.inst\n i.scale!(x, y)\n end\n\n def align(alignment)\n inst = self.inst\n inst.align!(alignment)\n end\n\n def transform(x: 0, y: 0, xscale: 1, yscale: 1, rot: 0, xposrot: 0, yposrot: 0)\n # update the tranform values\n inst = self.inst\n inst.transform!(x: x, y: y, xscale: xscale, yscale: yscale, rot: rot, xposrot: xposrot, yposrot: yposrot)\n end\n\n def translate_helper(x, y = 0)\n \"translate(#{x} #{y})\"\n end\n\n def rotate_helper(a, x = 0, y = 0)\n # a: degrees\n # x: rotate point\n # y: rotate point\n \"rotate(#{a} #{x} #{y})\"\n end\n\n\n def scale_helper(x, y = 1)\n # a: degrees\n # x: rotate point\n # y: rotate point\n \"scale(#{x} #{y})\"\n end\n\n def anchor_translate\n x = self.anchor_vector\n self.translate_helper(-x[-0], -x[1])\n end\n\n def apply_rotate\n self.rotate_helper(@rot, @xposrot, @yposrot)\n end\n\n def apply_scale\n self.scale_helper(@xscale, @yscale)\n end\n\n # ########################################################\n # Anchors\n # ########################################################\n\n def get_anchor_vector(alignment)\n anchor_matrix = self.parse_alignment alignment\n cw = @boundx / 2.0\n cy = @boundy / 2.0\n Vector[(anchor_matrix[0] + 1) * cw, (anchor_matrix[1] + 1) * cy]\n end\n\n def anchor_vector\n self.get_anchor_vector @alignment\n end\n\n def get_anchor alignment\n p = Vector[@x, @y]\n p + self.get_anchor_vector(alignment)\n end\n\n def anchor\n p = Vector[@x, @y]\n p + self.get_anchor_vector(@alignment)\n end\n\n def get_abs_anchor alignment\n p = Vector[@x, @y]\n p + self.get_abs_anchor_vector(alignment)\n end\n\n def abs_anchor\n self.get_abs_anchor(@alignment)\n end\n\n # multiple the anchor vector by the scaling vector\n def abs_anchor_vector\n return self.get_abs_anchor_vector(@alignment)\n end\n\n def get_abs_anchor_vector alignment\n a = self.get_anchor_vector(alignment)\n return Vector[@xscale * a[0], @yscale * a[1]]\n end\n\n def display_with_anchors\n if @name == 'debuganchor'\n return self\n end\n cross = SVGElement.new(name: \"debuganchor\", boundx: 10, boundy: 10)\n cross.add_child Tag.new('line', properties: {x1: 0, y1: 0, x2: 10, y2: 10, stroke: 'red', 'stroke-width' =\u003e 0.5})\n cross.add_child Tag.new('line', properties: {x2: 0, y1: 0, x1: 10, y2: 10, stroke: 'red', 'stroke-width' =\u003e 0.5})\n cross.add_child Tag.new('rect', properties: {x: 0, y: 0, width: 10, height: 10, stroke: 'red', fill: 'none', 'stroke-width' =\u003e 0.5})\n cross.scale!(0.75)\n cross.align!('center-center')\n inst = self.inst\n halign = ['left', 'center', 'right']\n valign = ['top', 'center', 'bottom']\n halign.each do |h|\n valign.each do |v|\n inst.add_child(cross.translate(*inst.get_anchor_vector(\"#{h}-#{v}\")))\n end\n end\n inst\n end\n\n def bb\n if @name == \"debuganchor\"\n return self\n end\n inst = self.inst\n ax, ay = self.anchor\n bounding_box = Tag.new('rect', properties: {\n x: 0,\n y: 0,\n width: @boundx,\n height: @boundy,\n \"stroke-width\" =\u003e 0.5,\n stroke: 'red',\n fill: 'none'\n })\n inst.add_child(bounding_box)\n end\n\n def parse_alignment(alignment)\n # parses an alignment string like 'center-left'\n\n tokens = alignment.split('-')\n if tokens.length != 2\n raise \"Property 'alignment=#{alignment}' is improperly formatted (e.g. use 'alignment=\\\"center-left\\\"')\"\n end\n\n if ['left', 'right'].include?(tokens[1])\n tokens[0], tokens[1] = tokens[1], tokens[0]\n end\n\n if ['upper', 'bottom', 'top'].include?(tokens[0])\n tokens[0], tokens[1] = tokens[1], tokens[0]\n end\n\n if ['left', 'right'].include?(tokens[1])\n raise \"Property 'alignment' not understood. Cannot be aligned to both left and right. Found '#{alignment}'\"\n end\n\n if ['top', 'upper', 'bottom'].include?(tokens[0])\n raise \"Property 'alignment' not understood. Cannot be aligned to both top and bottom. Found '#{alignment}'\"\n end\n\n anchor_dict = {\n left: -1,\n center: 0,\n right: 1,\n upper: -1,\n top: -1,\n bottom: 1\n }\n tokens.map {|t| anchor_dict[t.to_sym]}\n end\n\n # change relative coordinates to absolute coordinates\n def v(x, y)\n return Vector[x, y]\n end\n\n def abs_v(x, y)\n return Vector[@xscale * x, @yscale * y]\n end\n\n def style(mystyle)\n inst = self.inst\n inst.style!(mystyle)\n end\n\n def style!(mystyle)\n @style = Tag.new(\"style\", value: mystyle)\n end\n\n def svg(width=nil, height=nil, scale = 1.0)\n width = width || @boundx * @xscale\n height = height || @boundy * @yscale\n Tag.new('svg',\n value: [@style, self.g(id: \"svg\").to_str].join(''), properties: {\n width: \"#{width * scale}px\",\n height: \"#{height * scale}px\",\n viewBox: \"0 0 #{width} #{height}\",\n version: \"1.1\",\n xmlns: \"http://www.w3.org/2000/svg\"\n })\n end\n end\n\n class Shape \u003c SVGElement\n def initialize(x, y, shape, stroke = 'black', stroke_width = 1, shapevalue = nil, *args)\n\n super(*args)\n if @shape_properties.nil?\n @shape_properties = {}\n end\n @shape = shape\n @shapevalue = shapevalue\n self.update({stroke: stroke, \"stroke-width\" =\u003e stroke_width})\n end\n\n def dump\n elements_props = super\n {\n shape_properties: @shape_properties,\n shape: @shape,\n shapevalue: @shapevalue,\n element_props: elements_props\n }\n end\n\n def self.load(props)\n new = self.new(0, 0, props[:shape], 'black', 1, props[:shapevalue], **props[:element_props])\n new.update(props[:shape_properties])\n end\n\n def inst\n self.class.load(self.dump)\n end\n\n # def inst\n # Marshal.load(Marshal.dump(self))\n # end\n\n def get_child\n Tag.new(@shape, value: @shapevalue, properties: @shape_properties)\n end\n\n def update new_hash\n @shape_properties.merge!(new_hash)\n @children[0] = self.get_child\n self\n end\n end\n\n class Rect \u003c Shape\n def initialize(x, y, width, height, *args)\n @shape_properties = {width: width, height: height}\n super(x, y, 'rect', *args)\n @boundx = width\n @boundy = height\n end\n end\n\n class Circle \u003c Shape\n def initialize(x, y, r, *args)\n @shape_properties = {r: r, cx: 0, cy: 0}\n super(x, y, 'circle', *args)\n end\n end\n\n class Line \u003c Shape\n def initialize(x1, y1, x2, y2, *args)\n @shape_properties = {\n x1: x1,\n y1: y1,\n x2: x2,\n y2: y2}\n super(0, 0, 'line', *args)\n end\n end\n\n class VectorLine \u003c Shape\n def initialize(x, y, dx, dy, *args)\n @shape_properties = {x1: 0, y1: 0, x2: dx, y2: dy}\n super(x, y, 'line', *args)\n end\n end\n\n def label(text, properties = nil)\n properties = properties || {}\n font_size = properties[:font_size] || 12\n boundx = font_size * text.length * 0.5\n boundy = font_size\n mylabel = SVGElement.new(boundx: boundx, boundy: boundy)\n a = mylabel.get_anchor_vector('center-center')\n properties[:x] = a[0]\n properties[:y] = a[1]\n properties[\"alignment-baseline\".to_sym] = 'middle'\n properties[\"text-anchor\".to_sym] = 'middle'\n properties['font-family'.to_sym] = \"Verdana\"\n mylabel.add_child(\n Tag.new(\"text\", value: text, properties: properties)\n )\n end\n\n # class Label \u003c Shape\n # def initialize(label, font_size, x = 0, y = 0, font_family = \"Arial\", stroke='black', stroke_width=0, *args)\n # @shape_properties = {\n # x: 0, y: 0,\n # \"font-size\" =\u003e font_size,\n # \"font-family\" =\u003e font_family,\n # 'alignment-baseline' =\u003e 'middle',\n # 'text-anchor' =\u003e 'middle'}\n # label = label || \"\"\n # super(x, y, 'text', stroke, stroke_width, @shapevalue, *args)\n # @shapevalue = label\n # @boundy = font_size\n # @boundx = @shapevalue.length * font_size\n # self.update({})\n # end\n #\n # def self.load(props)\n # new = self.new(props[:shapevalue], 0, 0, 0, \"Arial\", \"black\", 0, props[:element_props])\n # new.update(props[:shape_properties])\n # end\n #\n # # Override x, y position so that label aligns with anchor\n # def get_child\n # s = @shape_properties.dup\n # av = self.get_anchor_vector('center-center')\n # s[:x] = av[0]\n # s[:y] = av[1]\n # Tag.new(@shape, value: @shapevalue, properties: s)\n # end\n #\n # # auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | inherit\n # def vertical_alignment(alignment)\n # self.update('alignment-baseline' =\u003e alignment)\n # end\n #\n # # start | middle | end | inherit\n # def text_anchor(alignment)\n # self.update('text-anchor' =\u003e alignment)\n # end\n # end\n\n # Organizes SVGElements on a grid of your choosing.\n # Makes it easier to position elements\n class SVGGrid \u003c SVGElement\n attr_accessor :elements\n\n def initialize(xnum, ynum, xspacing, yspacing, *args)\n super(*args)\n if @name == \"\"\n @name = \"grid\"\n end\n @xnum = xnum\n @ynum = ynum\n @xspacing = xspacing\n @yspacing = yspacing\n @boundx = self.boundx\n @boundy = self.boundy\n @elements = Array.new(xnum) {Array.new(ynum) {[]}} # 3d array for displaying some elements on a 2D grid\n end\n\n def dump\n element_props = super\n {\n xspacing: @xspacing,\n yspacing: @yspacing,\n xnum: @xnum,\n ynum: @ynum,\n element_props: element_props,\n elements: @elements\n }\n end\n\n def self.load(props)\n newgrid = SVGGrid.new(props[:xnum], props[:ynum], props[:xspacing], props[:yspacing], props[:element_props])\n newgrid.update_elements(props[:elements])\n newgrid\n end\n\n def update_elements elements\n if elements.length != @xnum\n raise \"Cannot update_elements. Number of rows must equal #{xnum} but was #{elements.length}\"\n end\n\n col_lengths = elements.map {|row| row.length}.uniq\n\n if col_lengths.length != 1\n raise \"Cannot update_elements. Rows have different number of columns.\"\n end\n\n if elements[0].length != @ynum\n raise \"Cannot update_elements. Number of columns must equal #{@ynum} but was #{elements[0].length}\"\n end\n\n @elements = elements\n end\n\n def inst\n self.class.load(self.dump)\n end\n\n # def inst\n # Marshal.load(Marshal.dump(self))\n # end\n\n\n def pos(r, c)\n return Vector[r * @xspacing, c * @yspacing]\n end\n\n def abs_pos_vector(r, c)\n v = self.pos(r, c)\n return Vector[@xscale * v[0], @yscale * v[1]]\n end\n\n def grid_coor(r, c, x, y)\n px, py = self.pos(r, c)\n return [px + x, py + y]\n end\n\n def grid_elements\n @elements.map.with_index do |row, r|\n x = self.pos(r, 0)[0]\n row_element = SVGElement.new(name: \"gridrow#{r}\").translate(x, 0)\n row.each.with_index do |element, c|\n y = pos(0, c)[1]\n col_element = SVGElement.new(name: \"gridcol#{c}\").translate(0, y)\n col_element.children = element\n\n # don't add empty columns\n if not element.nil? and not element == \"\" and not element == []\n row_element.add_child(col_element)\n end\n end\n\n # don't add empty rows\n row_element unless row_element.children.empty?\n end.compact\n end\n\n def children\n children = @children.dup\n grid_elements = self.grid_elements\n grid_elements + children\n end\n\n def group_children id: nil, classname: nil\n raise \"Cannot group children of a SVGGrid. Group using '.g' before grouping children\"\n end\n\n def add(element, x, y)\n xfloor = x.floor\n xrem = (x - xfloor).round(1)\n yfloor = y.floor\n yrem = (y - yfloor).round(1)\n\n if xrem \u003e 0 or yrem \u003e 0\n element = element.g(id: \"gridshift\").translate(xrem * @xspacing, yrem * @yspacing)\n end\n ele = @elements[xfloor][yfloor]\n if ele.nil?\n ele = []\n end\n ele.push(element)\n @elements[xfloor][yfloor] = ele\n end\n\n # TODO: maximum width from individual elements bounding boxes...\n def boundx\n @xnum * @xspacing\n end\n\n # TODO: maximum height from individual elements bounding boxes...\n def boundy\n @ynum * @yspacing\n end\n\n # Applies the block through each element in the grid\n def each\n raise \"#{self.class.name}.each needs a selection block\" unless block_given?\n @elements.each.with_index do |row, r|\n row.each.with_index do |col, c|\n col.each do |element|\n Proc.new.call(element)\n end\n end\n end\n end\n\n # Applies the block through each row, col in the grid\n def each_pos\n raise \"#{self.class.name}.each_pos needs a selection block\" unless block_given?\n @elements.each.with_index do |row, r|\n row.each.with_index do |col, c|\n Proc.new.call(r, c)\n end\n end\n end\n\n def elements_at(r, c)\n @elements[r][c]\n end\n\n def select\n raise \"#{self.class.name}.select needs a selection block\" unless block_given?\n selected = []\n @elements.each.with_index do |row, r|\n row.each.with_index do |col, c|\n if Proc.new.call(r, c)\n selected.push(col)\n end\n end\n end\n return selected\n end\n\n # def add_each_pos\n # raise \"#{self.class.name}.add_each_pos needs a selection block\" unless block_given?\n # @elements.each.with_index do |row, r|\n # row.each.with_index do |col, c|\n # new_element = Proc.new.call(r, c)\n # self.add(new_element, r, c)\n # end\n # end\n # end\n\n # return a copy of this grid with grid dots\n def griddots\n dot = Tag.new('circle', properties: {r: 3})\n inst = self.inst\n inst.each_pos do |r, c|\n inst.add(dot, r, c)\n end\n inst\n end\n end\n\n module MyGraphics\n attr_reader :tube, :openlid, :closedlid, :closedtube, :opentube, :detection_strip, :strip, :striplabel\n\n # bounding box for tube elements\n @@tubebb = SVGElement.new(boundx: 78.35, boundy: 242.95)\n\n def rarrow\n arrow = SVGElement.new(boundx: 33.48, boundy: 38.65)\n arrow.new_class!(\"rarrow\")\n arrow.add_child('\u003cpolygon points=\"0,0 33.477,19.328 0,38.655 \"/\u003e')\n end\n\n def larrow\n self.rarrow.mirror_horizontal.new_class(\"larrow\")\n end\n\n def uparrow\n arrow = SVGElement.new(boundx: 38.65, boundy: 33.48)\n arrow.new_class!(\"uparrow\")\n arrow.add_child('\u003cpolygon points=\"0,33.477 19.328,0 38.655,33.477 \"/\u003e')\n end\n\n def downarrow\n self.uparrow.mirror_vertical.new_class(\"downarrow\")\n end\n\n def tube\n _tube = @@tubebb.inst\n _tube.new_class!(\"tube\")\n _tube.add_child(\u003c\u003cEOF\n \u003cpath fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M4.75,99.697v45.309l14.998,90.066\n c0,4.35,5.036,7.875,11.25,7.875c6.215,0,11.25-3.525,11.25-7.875l15-90.066V99.697H4.75z\"/\u003e\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M61.998,95.697c0,2.199-1.799,4-4,4h-54c-2.2,0-4-1.801-4-4v-1.875c0-2.201,1.8-4,4-4h54\n c2.201,0,4,1.799,4,4V95.697z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M61.998,95.697c0,2.199-1.799,4-4,4h-54c-2.2,0-4-1.801-4-4v-1.875\n c0-2.201,1.8-4,4-4h54c2.201,0,4,1.799,4,4V95.697z\"/\u003e\n \u003c/g\u003e\nEOF\n )\n # \u003cline fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"7.721\" y1=\"123.572\" x2=\"53.387\" y2=\"123.572\"/\u003e\n _tube.inst\n end\n\n def tube2mL\n _tube = @@tubebb.inst\n _tube.new_class!(\"tube\")\n _tube.add_child(\u003c\u003cEOF\n \t\u003cg id=\"2mLTube\"\u003e\n\t\t\u003cpath id=\"_x32_mLTube\" fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M57,96.698H4.503v42.581l0.479,2.704\n\t\tc-0.311,1.553-0.479,3.153-0.479,4.792v70.798c0,13.955,11.812,25.374,26.249,25.374c14.436,0,26.248-11.419,26.248-25.374v-70.798\n\t\tc0-1.639-0.17-3.239-0.48-4.792l0.48-2.704V96.698z\"/\u003e\u003c/g\u003e\nEOF\n )\n _tube\n end\n\n def closedlid\n _closedlid = @@tubebb.inst\n _closedlid.new_class!(\"closedlid\")\n _closedlid.add_child(\u003c\u003cEOF\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M55.854,80.713c22.801,0,22.801,18.312,0,18.312c0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173\n C55.854,83.092,55.854,81.902,55.854,80.713z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M55.854,80.713c22.801,0,22.801,18.312,0,18.312\n c0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173C55.854,83.092,55.854,81.902,55.854,80.713z\"/\u003e\n \u003c/g\u003e\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M10.375,101.744c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688c0-1.1-0.535-2-1.188-2\n c-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2s-1.188,0.9-1.188,2\n V101.744z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M10.375,101.744c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688\n c0-1.1-0.535-2-1.188-2c-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2\n s-1.188,0.9-1.188,2V101.744z\"/\u003e\n \u003c/g\u003e\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M1,81.851c-0.55-0.952-0.101-1.731,1-1.731h55.473c1.1,0,2.311,0.845,2.689,1.877l1.146,3.121\n c0.381,1.032-0.209,1.877-1.309,1.877H5.972c-1.1,0-2.45-0.779-3-1.731L1,81.851z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M1,81.851c-0.55-0.952-0.101-1.731,1-1.731h55.473\n c1.1,0,2.311,0.845,2.689,1.877l1.146,3.121c0.381,1.032-0.209,1.877-1.309,1.877H5.972c-1.1,0-2.45-0.779-3-1.731L1,81.851z\"/\u003e\n \u003c/g\u003e\n \u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"72.809\" y1=\"92.338\" x2=\"73.953\" y2=\"92.338\"/\u003e\nEOF\n )\n _closedlid.inst\n end\n\n def openlid\n _openlid = @@tubebb.inst\n _openlid.new_class!(\"openlid\")\n _openlid.add_child(\u003c\u003cEOF\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M72.42,77.695c-3.271,7.512-10.102,12.477-16.996,13.795c0.375,1.254,0.75,2.506,1.125,3.76\n c17.402-5.207,26.029-24.734,18.164-41.105c-1.178,0.566-2.357,1.133-3.537,1.699C74.844,61.828,75.77,70.221,72.42,77.695z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M72.42,77.695c-3.271,7.512-10.102,12.477-16.996,13.795\n c0.375,1.254,0.75,2.506,1.125,3.76c17.402-5.207,26.029-24.734,18.164-41.105c-1.178,0.566-2.357,1.133-3.537,1.699\n C74.844,61.828,75.77,70.221,72.42,77.695z\"/\u003e\n \u003c/g\u003e\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M56.721,10.375c-1.1,0-2,0.9-2,2v37.25c0,1.1,0.9,2,2,2h0.688c1.1,0,2-0.534,2-1.188s0.9-1.188,2-1.188\n h8.938c1.1,0,2-0.9,2-2v-32.5c0-1.1-0.9-2-2-2h-8.938c-1.1,0-2-0.534-2-1.188s-0.9-1.188-2-1.188H56.721z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M56.721,10.375c-1.1,0-2,0.9-2,2v37.25c0,1.1,0.9,2,2,2h0.688\n c1.1,0,2-0.534,2-1.188s0.9-1.188,2-1.188h8.938c1.1,0,2-0.9,2-2v-32.5c0-1.1-0.9-2-2-2h-8.938c-1.1,0-2-0.534-2-1.188\n s-0.9-1.188-2-1.188H56.721z\"/\u003e\n \u003c/g\u003e\n \u003cg\u003e\n \u003cpath fill=\"#F7FCFE\" d=\"M76.613,1c0.953-0.55,1.732-0.1,1.732,1v55.471c0,1.1-0.846,2.311-1.877,2.69l-3.121,1.148\n c-1.033,0.38-1.877-0.21-1.877-1.31V5.971c0-1.1,0.779-2.45,1.73-3L76.613,1z\"/\u003e\n \u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M76.613,1c0.953-0.55,1.732-0.1,1.732,1v55.471\n c0,1.1-0.846,2.311-1.877,2.69l-3.121,1.148c-1.033,0.38-1.877-0.21-1.877-1.31V5.971c0-1.1,0.779-2.45,1.73-3L76.613,1z\"/\u003e\n \u003c/g\u003e\n \u003cline fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"60.408\" y1=\"47.721\" x2=\"60.408\" y2=\"14.471\"/\u003e\nEOF\n )\n _openlid.inst\n end\n\n def opentube\n _opentube = @@tubebb.inst\n _opentube.new_name!(\"opentube\")\n _opentube.add_child(self.openlid)\n _opentube.add_child(self.tube).inst\n end\n\n def closedtube\n _closedtube = @@tubebb.inst\n _closedtube.new_name!(\"closedtube\")\n _closedtube.add_child(self.closedlid)\n _closedtube.add_child(self.tube).inst\n end\n\n def strip\n mystrip = SVGElement.new(boundx: 83.1, boundy: 247.45)\n mystrip.add_child(\u003c\u003cEOF\n\u003cg id=\"Strip\"\u003e\n\t\u003cg\u003e\n\t\t\u003crect x=\"4.75\" fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"78.346\" height=\"242.948\"/\u003e\n\t\t\u003cline fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"0\" y1=\"247.448\" x2=\"4.75\" y2=\"242.948\"/\u003e\n\t\t\u003cpolygon fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"-0.067,4.777 4.75,0.001 4.75,242.948 0,247.448 \t\t\"/\u003e\n\t\t\u003cpolygon fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"74.917,247.448 0,247.448 4.75,242.948 83.096,242.948 \n\t\t\t\t\t\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg\u003e\n\t\t\u003crect x=\"19.583\" y=\"49.433\" fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"46.667\" height=\"80\"/\u003e\n\t\t\u003crect x=\"27.083\" y=\"57.433\" fill=\"#FFFFFF\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"31.667\" height=\"64\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"27.083\" y1=\"121.433\" x2=\"19.583\" y2=\"129.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"58.75\" y1=\"121.433\" x2=\"66.25\" y2=\"129.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"58.75\" y1=\"57.433\" x2=\"66.25\" y2=\"49.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"27.083\" y1=\"57.433\" x2=\"19.583\" y2=\"49.433\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg\u003e\n\t\t\u003cpath fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M57.524,216.515c0,4.385-3.693,7.938-8.249,7.938H36.557\n\t\t\tc-4.556,0-8.249-3.554-8.249-7.938v-22.164c0-4.385,3.693-7.939,8.249-7.939h12.718c4.556,0,8.249,3.554,8.249,7.939V216.515z\"/\u003e\n\t\t\u003cpath fill=\"#FFFFFF\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M52.917,213.019c0,3.002-2.528,5.435-5.647,5.435h-8.706\n\t\t\tc-3.119,0-5.647-2.433-5.647-5.435v-15.172c0-3.001,2.528-5.435,5.647-5.435h8.706c3.119,0,5.647,2.433,5.647,5.435V213.019z\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"34.01\" y1=\"216.224\" x2=\"30.27\" y2=\"221.647\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"51.823\" y1=\"216.224\" x2=\"55.562\" y2=\"221.647\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"51.823\" y1=\"194.642\" x2=\"55.023\" y2=\"188.663\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"34.01\" y1=\"194.195\" x2=\"30.27\" y2=\"189.666\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"32.917\" y1=\"205.433\" x2=\"28.308\" y2=\"205.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"52.917\" y1=\"205.433\" x2=\"57.524\" y2=\"205.433\"/\u003e\n\t\u003c/g\u003e\n\n\u003c/g\u003e\nEOF\n )\n end\n\n def fluid_small\n fluid = @@tubebb.inst\n fluid.new_class!(\"fluid\")\n fluid.new_name!(\"small_fluid\")\n fluid.add_child(\u003c\u003cEOF\n \u003cpath id=\"FluidSmall\" fill=\"#00AEEF\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M44.565,216.853\n\tc-12.031,0-12.031,8.833-24.062,8.833c-0.825,0-1.589-0.045-2.309-0.122l1.584,9.509c0,4.35,5.036,7.875,11.249,7.875\n\tc6.215,0,11.25-3.525,11.25-7.875l3.031-18.202C45.063,216.862,44.821,216.853,44.565,216.853z\"/\u003e\nEOF\n )\n end\n\n def fluid_medium\n fluid = @@tubebb.inst\n fluid.new_class!(\"fluid\")\n fluid.new_name!(\"med_fluid\")\n fluid.add_child(\u003c\u003cEOF\n\u003cpath id=\"FluidMedium\" fill=\"#00AEEF\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M44.315,166.187\n\tc-12.031,0-12.031,8.833-24.062,8.833c-5.585,0-8.576-1.904-11.383-3.944l10.657,63.997c0,4.35,5.036,7.875,11.249,7.875\n\tc6.215,0,11.25-3.525,11.25-7.875l11.101-66.649C50.918,167.142,48.268,166.187,44.315,166.187z\"/\u003e\nEOF\n )\n end\n\n def fluid_large\n fluid = @@tubebb.inst\n fluid.new_class!(\"fluid\")\n fluid.new_name!(\"small_fluid\")\n fluid.add_child(\u003c\u003cEOF\n\u003cpath id=\"FluidLarge\" fill=\"#BCE6FB\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M43.202,110.52\n\tc-12.031,0-12.031,8.833-24.062,8.833c-7.554,0-10.365-3.483-14.39-6.075v31.729l14.998,90.066c0,4.35,5.036,7.875,11.249,7.875\n\tc6.215,0,11.25-3.525,11.25-7.875l15-90.066v-28.64C53.402,113.803,50.538,110.52,43.202,110.52z\"/\u003e\nEOF\n )\n end\n\n def powder\n powder = @@tubebb.inst\n powder.new_class!(\"powder\")\n powder.new_name!(\"powder\")\n powder.add_child(\u003c\u003cEOF\n \u003cpath id=\"Powder\" fill=\"#FFFFFF\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M27.784,234.289c-0.647-2.643,1.036-2.308,2.842-2.495\n\tc1.183-0.124,3.538-0.179,4.792,0.55c0.33,0.957,1.645,1.147,1.775,1.945c0.106,0.649-1.18,1.446-1.407,1.983\n\tc-0.399,0.946,0.521,1.041-0.603,2.289c-0.534,0.593-2.338,1.107-3.088,1.463c-0.073,0.265-0.021,0.495-0.09,0.763\n\tc-1.498,0.401-7.79-0.416-4.875-2.518c-1.888-1.042-0.182-4.734,1.506-4.551\"/\u003e\nEOF\n )\n end\n\n def striplabel\n mylabel = SVGElement.new(boundx: 83.1, boundy: 247.45)\n mylabel.add_child(\u003c\u003cEOF\n\u003cg id=\"StripLabel\" class=\"fluid\"\u003e\n\t\u003crect x=\"4.75\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"78.346\" height=\"46.433\"/\u003e\n\u003c/g\u003e\nEOF\n )\n end\n\n def detection_strip\n mystrip = SVGElement.new(boundx: 83.1, boundy: 247.45)\n mystrip.add_child(self.strip)\n mystrip.add_child(self.striplabel)\n end\n\n def control_band\n band = SVGElement.new(boundx: 83.1, boundy: 247.45)\n band.add_child(\u003c\u003cEOF\n\u003cline id=\"ControlBand\" fill=\"none\" stroke=\"#F7A7AB\" stroke-width=\"6\" stroke-miterlimit=\"10\" x1=\"27.083\" y1=\"68.432\" x2=\"58.75\" y2=\"68.432\"/\u003e\nEOF\n )\n end\n\n def wt_band\n band = SVGElement.new(boundx: 83.1, boundy: 247.45)\n band.add_child(\u003c\u003cEOF\n\u003cline id=\"WTBand\" fill=\"none\" stroke=\"#F7A7AB\" stroke-width=\"6\" stroke-miterlimit=\"10\" x1=\"27.089\" y1=\"89.433\" x2=\"58.756\" y2=\"89.433\"/\u003e\nEOF\n )\n end\n\n def mut_band\n band = SVGElement.new(boundx: 83.1, boundy: 247.45)\n band.add_child(\u003c\u003cEOF\n\u003cline id=\"MutantBand\" fill=\"none\" stroke=\"#F7A7AB\" stroke-width=\"6\" stroke-miterlimit=\"10\" x1=\"27.089\" y1=\"111.099\" x2=\"58.756\" y2=\"111.099\"/\u003e\nEOF\n )\n end\n end\n\n ########################################################################\n # ####\n # ####\n # #### GRAPHICS TESTING\n # ####\n # ####\n ########################################################################\n\n\n def save_svg(filename, svg)\n File.write(filename, svg.to_str)\n end\nend\n\n\n"}},{"library":{"name":"SVGGraphicsImages","category":"SVGGraphics","code_source":"# require_relative 'graphics'\nneeds \"SVGGraphics/GraphicsBase\"\n\nmodule OLAGraphics\n include Graphics\n include Graphics::MyGraphics\n\n @@colors = [\"red\", \"yellow\", \"green\", \"blue\", \"purple\"]\n\n def self.set_tube_colors(new_colors)\n @@colrs = new_colors\n end\n\n\n #####################################\n # BASICS\n #####################################\n\n def get_style\n \u003c\u003cEOF\n /* \u003c![CDATA[ */\n \n #svg .yellow path {\n fill: #f7f9c2;\n }\n \n #svg .white rect {\n fill: #ffffff;\n }\n \n #svg .blue path {\n fill: #bdf8f9;\n }\n \n #svg .red path {\n fill: #ffc4c4;\n }\n \n #svg .green path {\n fill: #c4f9c2;\n }\n \n #svg .purple path {\n fill: #f1e0fc;\n }\n\n #svg .hidden {\n opacity: 0.3;\n }\n\n #svg .yellowstrip rect {\n fill: #f7f9c2;\n }\n \n #svg .bluestrip rect {\n fill: #bdf8f9;\n }\n \n #svg .whitestrip rect {\n fill: #ffffff;\n }\n \n #svg .redstrip rect {\n fill: #ffc4c4;\n }\n \n #svg .greenstrip rect {\n fill: #c4f9c2;\n }\n \n #svg .purplestrip rect {\n fill: #f1e0fc;\n }\n\n #svg .redfluid path {\n fill: #ff7c66;\n }\n \n #svg .brownfluid path {\n fill: #8B4513;\n }\n\n #svg .pinkfluid path {\n fill: #ff8eec;\n }\n \n #svg .palefluid path {\n fill: #F2F5D1;\n }\n /* ]]\u003e */\nEOF\n end\n\n def display_svg(element, scale = 1)\n element.style!(self.get_style)\n element.svg(element.boundx, element.boundy, scale).to_str\n end\n\n # two labels on top of each other\n def two_labels(text1, text2)\n label1 = label(text1, \"font-size\".to_sym =\u003e 25)\n label2 = label(text2, \"font-size\".to_sym =\u003e 25)\n label2.align!('center-top')\n label2.align_with(label1, 'center-bottom')\n label2.translate!(0, 12)\n SVGElement.new(children: [label1, label2], boundx: label1.boundx, boundy: label1.boundy * 2)\n end\n\n # make a tube label\n def tube_label(kit, unit, component, sample)\n self.two_labels(\"#{kit}#{unit}\", \"#{component}#{sample}\")\n end\n\n def make_arrow(from, to, tlabel = nil, blabel = nil, tfontsize = 25, bfontsize = 25)\n # make right arrow\n top_label = label(tlabel, \"font-size\".to_sym =\u003e tfontsize)\n bottom_label = label(blabel, \"font-size\".to_sym =\u003e bfontsize)\n arrow = rarrow.scale(0.75)\n arrow.align!('center-right')\n arrow.align_with(to, 'center-left')\n v1 = from.get_abs_anchor('center-right') - from.abs_anchor_vector\n v2 = to.get_abs_anchor('center-left') - to.abs_anchor_vector\n m = (v1 + v2) / 2.0\n line = Line.new(*v1, *v2, 'black', 3)\n unless top_label.nil?\n top_label.translate!(*m)\n top_label.align!('center-bottom').translate!(0, -10)\n end\n unless bottom_label.nil?\n bottom_label.translate!(*m)\n puts bottom_label\n bottom_label.align!('center-top').translate!(0, 10)\n end\n myarrow = SVGElement.new(children: [line, arrow, bottom_label, top_label].compact)\n end\n\n def make_transfer(from, to, spacing, top_label, bottom_label)\n to.align_with(from, 'center-right').align!('center-left')\n to.translate!(spacing)\n arrow = make_arrow(from, to, top_label, bottom_label)\n elements = [arrow, from, to]\n puts elements.map {|e| Vector[e.x, e.y] + e.get_abs_anchor('center-right')}\n max_x = elements.map {|e| (Vector[e.x, e.y] + e.get_abs_anchor('center-right'))[0]}.max\n max_y = elements.map {|e| (Vector[e.x, e.y] + e.get_abs_anchor('center-bottom'))[1]}.max\n svg = SVGElement.new(\n children: elements,\n boundx: 700,\n boundy: 300,\n )\n svg.translate!(20)\n end\n\n def make_tube(tube, bottom_label, middle_label, fluid = nil, cropped_for_closed_tube = false, fluidclass: nil)\n bottom_label = bottom_label.join(\"\\n\") if bottom_label.is_a?(Array)\n middle_label = middle_label.join(\"\\n\") if middle_label.is_a?(Array)\n img = SVGElement.new(boundx: tube.boundx, boundy: tube.boundy)\n tube_group = tube\n bottom_labels = bottom_label.split(\"\\n\")\n middle_labels = middle_label.split(\"\\n\")\n\n img.add_child(tube)\n fluidImage = nil\n if fluid == \"small\"\n fluidImage = fluid_small\n elsif fluid == \"medium\"\n fluidImage = fluid_medium\n elsif fluid == \"large\"\n fluidImage = fluid_large\n elsif fluid == \"powder\"\n fluidImage = powder\n end\n puts fluid\n fluidImage.new_class!(fluidclass) unless fluidImage.nil? or fluidclass.nil?\n img.add_child(fluidImage) unless fluidImage.nil?\n puts fluid\n if bottom_label != \"\"\n bl = nil\n if bottom_labels.length == 2\n bl = two_labels(*bottom_labels)\n else\n label = label(bottom_label, \"font-size\".to_sym =\u003e 25)\n bl = label\n end\n bl.align!('center-top')\n bl.align_with(tube, 'center-bottom')\n bl.translate!(-5 * tube.xscale, 5 * tube.yscale)\n tube.boundy = tube.boundy + bl.boundy\n img.add_child(bl)\n end\n\n if middle_label != \"\"\n ml = nil\n if middle_labels.length == 2\n ml = two_labels(*middle_labels)\n else\n ml = label(middle_label, \"font-size\".to_sym =\u003e 25)\n end\n ml.align!('center-center')\n ml.align_with(tube, 'center-bottom')\n ml.translate!(-9 * tube.xscale, -110 * tube.yscale)\n img.add_child(ml)\n end\n\n\n img.boundx = tube.boundx\n img.boundy = tube.boundy\n if cropped_for_closed_tube\n shift = 70\n img.boundy = img.boundy - shift\n img.group_children.translate!(0, -shift)\n end\n img.translate!(10)\n end\n\n #####################################\n # LIGATIONS\n #####################################\n\n def display_ligation_tubes(kit, unit, components, sample, colors, open_tubes = nil, hide = nil, spacing = 70)\n def stripwell(kit, unit, components, sample, open_tubes, apply_classes, hide, spacing)\n open_tubes = open_tubes || []\n hide = hide || []\n apply_classes = apply_classes || []\n num = components.length\n grid = SVGGrid.new(num, 1, spacing, opentube.boundy)\n grid.each_pos do |r, c|\n\n # add label\n tube_label = self.tube_label(kit, unit, components[r], sample)\n tube_type = closedtube\n if open_tubes.include?(r)\n tube_type = opentube\n end\n tube = make_tube(tube_type,\n \"\",\n [\"#{kit}#{unit}\", \"#{components[r]}#{sample}\"],\n nil,\n false)\n tube.new_class!(apply_classes[r])\n if hide.include?(r)\n tube = tube.g(classname: 'hidden')\n end\n grid.add(tube, r, c)\n end\n grid\n end\n\n mystripwell = stripwell(kit, unit, components, sample, open_tubes, colors, hide, spacing).scale!(0.75)\n myimage = SVGElement.new(boundx: 500, boundy: 190)\n myimage.add_child(mystripwell)\n end\n\n def highlight_ligation_tube(i, kit, unit, components, sample, colors)\n ligation_tubes = self.display_ligation_tubes(\n kit, unit, components, sample, colors, [i], (0..components.length - 1).to_a.reject {|x| x == i})\n ligation_tubes\n end\n\n def transfer_to_ligation_tubes_with_highlight(from, i, kit, unit, components, sample, colors, vol, bottom_label = nil)\n bottom_label = bottom_label || \"\"\n ligation_tubes = self.highlight_ligation_tube(i, kit, unit, components, sample, colors)\n ligation_tubes.align_with(from, 'center-right').align!('center-left')\n ligation_label = label(\"ligation tubes\", \"font-size\".to_sym =\u003e 25)\n # ligation_label.align_with(ligation_tubes, 'center-bottom').align!('center-top')\n svg = self.make_transfer(from, ligation_tubes, 200, \"#{vol}uL\", bottom_label)\n svg.translate!(20)\n svg.boundy = svg.boundy - 20\n svg.boundx = 700\n svg.boundy = 295\n svg\n end\n\n #####################################\n # DETECTION\n #####################################\n\n def display_strip_panel(kit, unit, components, sample, colors)\n def panel kit, unit, components, sample, apply_classes\n apply_classes = apply_classes || []\n num = components.length\n strip = make_strip(nil, \"\")\n grid = SVGGrid.new(num, 1, 90, strip.boundy)\n grid.each_pos do |r, c|\n\n # add label\n strip_label = self.tube_label(kit, unit, components[r], sample).scale(0.8)\n strip = make_strip(strip_label, apply_classes[r] + \"strip\")\n grid.add(strip, r, c)\n end\n grid.scale!(0.75)\n end\n \n mypanel = panel(kit, unit, components, sample, colors)\n mypanel.boundx = 600\n mypanel\n end\n\n def display_panel_and_tubes(kit, panel_unit, tube_unit, components, sample, colors)\n tubes = display_ligation_tubes(kit, tube_unit, components, sample, colors)\n panel = display_strip_panel(kit, panel_unit, components, sample, colors)\n tubes.align_with(panel, 'center-bottom')\n tubes.align!('center-top')\n tubes.translate!(0, -50)\n img = SVGElement.new(children: [tubes, panel], boundy: 330, boundx: panel.boundx)\n end\n\n def make_strip mylabel, classname\n mystrip = SVGElement.new(boundx: 83.1, boundy: 247.45)\n mystrip.add_child(self.strip)\n mystrip.add_child(self.striplabel.new_class(classname))\n # mylabel = label(\"Strip\", \"font-size\".to_sym=\u003e20)\n unless mylabel.nil?\n mylabel.align_with(mystrip, 'center-top')\n mylabel.align!('center-center')\n mylabel.translate!(0, 20)\n mystrip.add_child(mylabel)\n end\n mystrip\n end\n\n def detection_strip_diagram\n img = SVGElement.new(boundx: 270, boundy: 270)\n img.add_child(\u003c\u003cEOF\n \u003cg id=\"Strip\"\u003e\n\t\u003cg\u003e\n\t\t\u003crect x=\"4.75\" fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"78.346\" height=\"242.948\"/\u003e\n\t\t\u003cline fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"0\" y1=\"247.448\" x2=\"4.75\" y2=\"242.948\"/\u003e\n\t\t\u003cpolygon fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"-0.067,4.777 4.75,0.001 4.75,242.948 0,247.448 \t\t\"/\u003e\n\t\t\u003cpolygon fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"74.917,247.448 0,247.448 4.75,242.948 83.096,242.948 \n\t\t\t\t\t\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg\u003e\n\t\t\u003crect x=\"19.583\" y=\"49.433\" fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"46.667\" height=\"80\"/\u003e\n\t\t\u003crect x=\"27.083\" y=\"57.433\" fill=\"#FFFFFF\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"31.667\" height=\"64\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"27.083\" y1=\"121.433\" x2=\"19.583\" y2=\"129.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"58.75\" y1=\"121.433\" x2=\"66.25\" y2=\"129.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"58.75\" y1=\"57.433\" x2=\"66.25\" y2=\"49.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"27.083\" y1=\"57.433\" x2=\"19.583\" y2=\"49.433\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg\u003e\n\t\t\u003cpath fill=\"#E6E7E8\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M57.524,216.515c0,4.385-3.693,7.938-8.249,7.938H36.557\n\t\t\tc-4.556,0-8.249-3.554-8.249-7.938v-22.164c0-4.385,3.693-7.939,8.249-7.939h12.718c4.556,0,8.249,3.554,8.249,7.939V216.515z\"/\u003e\n\t\t\u003cpath fill=\"#FFFFFF\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M52.917,213.019c0,3.002-2.528,5.435-5.647,5.435h-8.706\n\t\t\tc-3.119,0-5.647-2.433-5.647-5.435v-15.172c0-3.001,2.528-5.435,5.647-5.435h8.706c3.119,0,5.647,2.433,5.647,5.435V213.019z\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"34.01\" y1=\"216.224\" x2=\"30.27\" y2=\"221.647\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"51.823\" y1=\"216.224\" x2=\"55.562\" y2=\"221.647\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"51.823\" y1=\"194.642\" x2=\"55.023\" y2=\"188.663\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"34.01\" y1=\"194.195\" x2=\"30.27\" y2=\"189.666\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"32.917\" y1=\"205.433\" x2=\"28.308\" y2=\"205.433\"/\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"52.917\" y1=\"205.433\" x2=\"57.524\" y2=\"205.433\"/\u003e\n\t\u003c/g\u003e\n\u003c/g\u003e\n\u003cg id=\"StripLabel\"\u003e\n\t\u003crect x=\"4.75\" fill=\"#ED1C24\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"78.346\" height=\"46.433\"/\u003e\n\u003c/g\u003e\n\u003cpolygon fill=\"#BBC9E7\" stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"43.923,198.542 48.016,201.414 166.567,59.148 \n\t141.463,41.534 \"/\u003e\n\u003ctext transform=\"matrix(1 0 0 1 -60.75 216.2236)\" font-family=\"'MyriadPro-Regular'\" font-size=\"20\"\u003ePort\u003c/text\u003e\n\u003ctext transform=\"matrix(1 0 0 1 -87.2627 91.1001)\"\u003e\u003ctspan x=\"0\" y=\"0\" font-family=\"'MyriadPro-Regular'\" font-size=\"20\"\u003eReading\u003c/tspan\u003e\u003ctspan x=\"0\" y=\"24\" font-family=\"'MyriadPro-Regular'\" font-size=\"20\"\u003eWindow\u003c/tspan\u003e\u003c/text\u003e\n\u003cline fill=\"none\" stroke=\"#000000\" stroke-width=\"4\" stroke-miterlimit=\"10\" x1=\"-11.417\" y1=\"95.1\" x2=\"32.917\" y2=\"94.1\"/\u003e\n\u003cline fill=\"none\" stroke=\"#000000\" stroke-width=\"4\" stroke-miterlimit=\"10\" x1=\"-17.417\" y1=\"209.401\" x2=\"26.917\" y2=\"208.401\"/\u003e\nEOF\n )\n img.translate!(100)\n return img\n end\n\n def negative_selection_diagram\n img = SVGElement.new(boundx: 600, boundy: 262)\n img.add_child(\u003c\u003cEOF\n\u003cg id=\"SingleTubes_3_\"\u003e\n\t\u003cg id=\"ClosedLid_3_\"\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M363.205,46.889c22.801,0,22.801,18.312,0,18.312c0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173\n\t\t\t\tC363.205,49.268,363.205,48.078,363.205,46.889z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M363.205,46.889c22.801,0,22.801,18.312,0,18.312\n\t\t\t\tc0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173C363.205,49.268,363.205,48.078,363.205,46.889z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M317.727,67.92c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688c0-1.1-0.535-2-1.188-2\n\t\t\t\tc-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2s-1.188,0.9-1.188,2\n\t\t\t\tV67.92z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M317.727,67.92c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688\n\t\t\t\tc0-1.1-0.535-2-1.188-2c-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2\n\t\t\t\ts-1.188,0.9-1.188,2V67.92z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M308.352,48.026c-0.55-0.952-0.1-1.731,1-1.731h55.473c1.1,0,2.311,0.845,2.689,1.877l1.146,3.121\n\t\t\t\tc0.381,1.032-0.209,1.877-1.309,1.877h-54.028c-1.101,0-2.45-0.779-3.001-1.731L308.352,48.026z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M308.352,48.026c-0.55-0.952-0.1-1.731,1-1.731h55.473\n\t\t\t\tc1.1,0,2.311,0.845,2.689,1.877l1.146,3.121c0.381,1.032-0.209,1.877-1.309,1.877h-54.028c-1.101,0-2.45-0.779-3.001-1.731\n\t\t\t\tL308.352,48.026z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"380.16\" y1=\"58.514\" x2=\"381.305\" y2=\"58.514\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg id=\"Tube_3_\"\u003e\n\t\t\u003cpath fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M312.102,65.873v45.309l14.998,90.066\n\t\t\tc0,4.35,5.037,7.875,11.25,7.875c6.215,0,11.25-3.525,11.25-7.875l15-90.066V65.873H312.102z\"/\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M369.35,61.873c0,2.199-1.799,4-4,4h-54c-2.199,0-4-1.801-4-4v-1.875c0-2.201,1.801-4,4-4h54\n\t\t\t\tc2.201,0,4,1.799,4,4V61.873z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M369.35,61.873c0,2.199-1.799,4-4,4h-54c-2.199,0-4-1.801-4-4\n\t\t\t\tv-1.875c0-2.201,1.801-4,4-4h54c2.201,0,4,1.799,4,4V61.873z\"/\u003e\n\t\t\u003c/g\u003e\n\t\u003c/g\u003e\n\u003c/g\u003e\n\u003cg id=\"SingleTubes_1_\"\u003e\n\t\u003cg id=\"ClosedLid_1_\"\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M175.96,48.821c22.801,0,22.801,18.312,0,18.312c0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173\n\t\t\t\tC175.96,51.2,175.96,50.011,175.96,48.821z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M175.96,48.821c22.801,0,22.801,18.312,0,18.312\n\t\t\t\tc0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173C175.96,51.2,175.96,50.011,175.96,48.821z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M130.481,69.853c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688c0-1.1-0.535-2-1.188-2\n\t\t\t\tc-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2s-1.188,0.9-1.188,2\n\t\t\t\tV69.853z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M130.481,69.853c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688\n\t\t\t\tc0-1.1-0.535-2-1.188-2c-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2\n\t\t\t\ts-1.188,0.9-1.188,2V69.853z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M121.106,49.959c-0.55-0.952-0.1-1.731,1-1.731h55.473c1.1,0,2.311,0.845,2.689,1.877l1.146,3.121\n\t\t\t\tc0.381,1.032-0.209,1.877-1.309,1.877h-54.028c-1.101,0-2.45-0.779-3.001-1.731L121.106,49.959z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M121.106,49.959c-0.55-0.952-0.1-1.731,1-1.731h55.473\n\t\t\t\tc1.1,0,2.311,0.845,2.689,1.877l1.146,3.121c0.381,1.032-0.209,1.877-1.309,1.877h-54.028c-1.101,0-2.45-0.779-3.001-1.731\n\t\t\t\tL121.106,49.959z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"192.915\" y1=\"60.446\" x2=\"194.06\" y2=\"60.446\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg id=\"Tube_1_\"\u003e\n\t\t\u003cpath fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M124.856,67.806v45.309l14.998,90.066\n\t\t\tc0,4.35,5.037,7.875,11.25,7.875c6.215,0,11.25-3.525,11.25-7.875l15-90.066V67.806H124.856z\"/\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M182.104,63.806c0,2.2-1.8,4-4,4h-54c-2.2,0-4-1.8-4-4v-1.875c0-2.2,1.8-4,4-4h54c2.2,0,4,1.8,4,4V63.806\n\t\t\t\tz\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M182.104,63.806c0,2.2-1.8,4-4,4h-54c-2.2,0-4-1.8-4-4v-1.875\n\t\t\t\tc0-2.2,1.8-4,4-4h54c2.2,0,4,1.8,4,4V63.806z\"/\u003e\n\t\t\u003c/g\u003e\n\t\u003c/g\u003e\n\u003c/g\u003e\n\u003cg id=\"SingleTubes_2_\"\u003e\n\t\u003cg id=\"ClosedLid_2_\"\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M272.545,47.965c22.801,0,22.801,18.312,0,18.312c0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173\n\t\t\t\tC272.545,50.344,272.545,49.154,272.545,47.965z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M272.545,47.965c22.801,0,22.801,18.312,0,18.312\n\t\t\t\tc0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173C272.545,50.344,272.545,49.154,272.545,47.965z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M227.066,68.996c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688c0-1.101-0.534-2-1.188-2\n\t\t\t\ts-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2s-1.188,0.899-1.188,2V68.996z\"\n\t\t\t\t/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M227.066,68.996c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688\n\t\t\t\tc0-1.101-0.534-2-1.188-2s-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2\n\t\t\t\ts-1.188,0.899-1.188,2V68.996z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M217.691,49.103c-0.55-0.953-0.1-1.732,1-1.732h55.473c1.1,0,2.311,0.845,2.69,1.877l1.146,3.121\n\t\t\t\tc0.38,1.032-0.21,1.877-1.31,1.877h-54.028c-1.1,0-2.45-0.779-3-1.732L217.691,49.103z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M217.691,49.103c-0.55-0.953-0.1-1.732,1-1.732h55.473\n\t\t\t\tc1.1,0,2.311,0.845,2.69,1.877l1.146,3.121c0.38,1.032-0.21,1.877-1.31,1.877h-54.028c-1.1,0-2.45-0.779-3-1.732L217.691,49.103z\n\t\t\t\t\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"289.5\" y1=\"59.59\" x2=\"290.645\" y2=\"59.59\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg id=\"Tube_2_\"\u003e\n\t\t\u003cpath fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M221.441,66.949v45.309l14.998,90.066\n\t\t\tc0,4.35,5.037,7.875,11.25,7.875c6.215,0,11.25-3.525,11.25-7.875l15-90.066V66.949H221.441z\"/\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M278.689,62.949c0,2.2-1.8,4-4,4h-54c-2.2,0-4-1.8-4-4v-1.875c0-2.2,1.8-4,4-4h54c2.2,0,4,1.8,4,4V62.949\n\t\t\t\tz\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M278.689,62.949c0,2.2-1.8,4-4,4h-54c-2.2,0-4-1.8-4-4v-1.875\n\t\t\t\tc0-2.2,1.8-4,4-4h54c2.2,0,4,1.8,4,4V62.949z\"/\u003e\n\t\t\u003c/g\u003e\n\t\u003c/g\u003e\n\u003c/g\u003e\n\u003cg id=\"SingleTubes_4_\"\u003e\n\t\u003cg id=\"ClosedLid_4_\"\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M459.754,47.131c22.801,0,22.801,18.312,0,18.312c0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173\n\t\t\t\tC459.754,49.51,459.754,48.32,459.754,47.131z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M459.754,47.131c22.801,0,22.801,18.312,0,18.312\n\t\t\t\tc0-1.189,0-2.38,0-3.57c13.912,0,13.912-11.173,0-11.173C459.754,49.51,459.754,48.32,459.754,47.131z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M414.275,68.162c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688c0-1.1-0.535-2-1.188-2\n\t\t\t\tc-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2s-1.188,0.9-1.188,2\n\t\t\t\tV68.162z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M414.275,68.162c0,1.1,0.9,2,2,2h37.25c1.1,0,2-0.9,2-2v-0.688\n\t\t\t\tc0-1.1-0.535-2-1.188-2c-0.654,0-1.188-0.9-1.188-2v-8.938c0-1.1-0.9-2-2-2h-32.5c-1.1,0-2,0.9-2,2v8.938c0,1.1-0.534,2-1.188,2\n\t\t\t\ts-1.188,0.9-1.188,2V68.162z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M404.9,48.269c-0.55-0.952-0.1-1.731,1-1.731h55.473c1.1,0,2.311,0.845,2.689,1.877l1.146,3.121\n\t\t\t\tc0.381,1.032-0.209,1.877-1.309,1.877h-54.028c-1.101,0-2.45-0.779-3.001-1.731L404.9,48.269z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M404.9,48.269c-0.55-0.952-0.1-1.731,1-1.731h55.473\n\t\t\t\tc1.1,0,2.311,0.845,2.689,1.877l1.146,3.121c0.381,1.032-0.209,1.877-1.309,1.877h-54.028c-1.101,0-2.45-0.779-3.001-1.731\n\t\t\t\tL404.9,48.269z\"/\u003e\n\t\t\u003c/g\u003e\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" x1=\"476.709\" y1=\"58.756\" x2=\"477.854\" y2=\"58.756\"/\u003e\n\t\u003c/g\u003e\n\t\u003cg id=\"Tube_4_\"\u003e\n\t\t\u003cpath fill=\"#F7FCFE\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M408.65,66.115v45.309l14.998,90.066\n\t\t\tc0,4.35,5.037,7.875,11.25,7.875c6.215,0,11.25-3.525,11.25-7.875l15-90.066V66.115H408.65z\"/\u003e\n\t\t\u003cg\u003e\n\t\t\t\u003cpath fill=\"#F7FCFE\" d=\"M465.898,62.115c0,2.199-1.799,4-4,4h-54c-2.199,0-4-1.801-4-4V60.24c0-2.201,1.801-4,4-4h54\n\t\t\t\tc2.201,0,4,1.799,4,4V62.115z\"/\u003e\n\t\t\t\u003cpath fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M465.898,62.115c0,2.199-1.799,4-4,4h-54c-2.199,0-4-1.801-4-4\n\t\t\t\tV60.24c0-2.201,1.801-4,4-4h54c2.201,0,4,1.799,4,4V62.115z\"/\u003e\n\t\t\u003c/g\u003e\n\t\u003c/g\u003e\n\u003c/g\u003e\n\u003crect x=\"180.021\" y=\"67.951\" fill=\"#58595B\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"16.705\" height=\"143.104\"/\u003e\n\u003crect x=\"276.023\" y=\"68.36\" fill=\"#58595B\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"16.705\" height=\"143.104\"/\u003e\n\u003crect x=\"366.934\" y=\"66.539\" fill=\"#58595B\" stroke=\"#000000\" stroke-miterlimit=\"10\" width=\"16.705\" height=\"143.104\"/\u003e\n\u003cg\u003e\n\t\u003cpath fill=\"#F2F5D1\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M151.104,211.465c6.215,0,11.25-3.525,11.25-7.875l15-90.066\n\t\tV92.904c-3.914-4.414-7.246-9.508-14.937-9.508c-13.567,0-13.567,15.856-27.136,15.856c-4.752,0-7.837-1.947-10.426-4.476v18.746\n\t\tl14.998,90.066C139.854,207.939,144.891,211.465,151.104,211.465z\"/\u003e\n\t\u003cpath id=\"largegoop_1_\" fill=\"#BE1E2D\" d=\"M176.233,99.027c-0.539-0.592-1.101-1.156-1.7-1.677\n\t\tc-2.18-1.891-4.767-3.295-7.818-4.106c-3.108-0.959-5.194-0.299-6.55,1.576c-1.355,1.877-1.983,4.971-2.176,8.881\n\t\tc-0.043,4.081,0.029,8.187,0.148,12.31c-0.371,3.683-0.492,7.357-0.467,11.022c0.897,18.184,3.846,37.165-0.056,56.177\n\t\tc-0.8,1.744-1.673,3.365-2.61,4.925c-3.899,3.502-12.119,3.583-12.802,9.137c-1.465,7.144,6.075,12.614,13.855,11.348\n\t\tc1.231-0.126,2.429-0.324,3.597-0.573c0.996-1.113,1.578-2.408,1.578-3.796l15-85.266V99.027z\"/\u003e\n\u003c/g\u003e\n\u003cg\u003e\n\t\u003cpath fill=\"#F2F5D1\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M338.349,210.596c6.215,0,11.25-3.525,11.25-7.875l15-90.066\n\t\tV92.035c-3.914-4.414-7.246-9.508-14.937-9.508c-13.567,0-13.567,15.856-27.136,15.856c-4.752,0-7.837-1.947-10.426-4.476v18.746\n\t\tl14.998,90.066C327.099,207.07,332.136,210.596,338.349,210.596z\"/\u003e\n\t\u003cpath id=\"smallgoop_1_\" fill=\"#BE1E2D\" d=\"M351.601,187.893c-0.48,0.29-0.853,0.667-1.167,1.104\n\t\tc-0.286,0.446-0.544,0.889-0.791,1.329c-0.345,0.425-0.624,0.832-0.864,1.229c-0.991,1.92-1.5,3.797-3.802,6.113\n\t\tc-0.326,0.24-0.664,0.473-1.014,0.702c-1.255,0.63-3.409,1.163-3.962,1.811c-0.864,0.87,0.737,0.984,2.856,0.351\n\t\tc2.816-0.787,5.354-1.938,7.215-3.107c0.564-0.939,0.878-1.972,0.878-3.055l1.118-6.713\n\t\tC351.903,187.729,351.745,187.806,351.601,187.893z\"/\u003e\n\u003c/g\u003e\n\u003cg\u003e\n\t\u003cpath fill=\"#F2F5D1\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M247.69,210.199c6.215,0,11.25-3.525,11.25-7.875l15-90.066\n\t\tV91.639c-3.914-4.414-7.246-9.508-14.937-9.508c-13.567,0-13.567,15.856-27.136,15.856c-4.752,0-7.837-1.947-10.426-4.476v18.746\n\t\tl14.998,90.066C236.44,206.674,241.478,210.199,247.69,210.199z\"/\u003e\n\t\u003cpath id=\"mediumgoop_1_\" fill=\"#BE1E2D\" d=\"M265.456,155.475c-1.722-0.177-2.978,0.252-3.904,1.138s-1.526,2.229-1.938,3.884\n\t\tc-0.338,1.716-0.611,3.433-0.859,5.154c-0.497,1.573-0.849,3.123-1.115,4.659c-0.877,7.564-0.631,15.315-4.343,23.576\n\t\tc-0.596,0.789-1.225,1.533-1.886,2.255c-2.521,1.753-7.274,2.385-8.095,4.764c-1.395,3.104,2.541,4.853,7.132,3.756\n\t\tc3.25-0.642,6.229-1.791,8.848-3.264c0.022-0.203,0.044-0.406,0.044-0.613l7.516-45.127\n\t\tC266.412,155.575,265.951,155.51,265.456,155.475z\"/\u003e\n\u003c/g\u003e\n\u003cg\u003e\n\t\u003cpath fill=\"#F2F5D1\" stroke=\"#000000\" stroke-miterlimit=\"10\" d=\"M434.899,209.771c6.215,0,11.25-3.525,11.25-7.875l15-90.066\n\t\tV91.211c-3.914-4.414-7.246-9.508-14.937-9.508c-13.567,0-13.567,15.856-27.136,15.856c-4.752,0-7.837-1.947-10.426-4.476v18.746\n\t\tl14.998,90.066C423.649,206.246,428.687,209.771,434.899,209.771z\"/\u003e\n\u003c/g\u003e\n\u003ctext transform=\"matrix(1 0 0 1 11.918 253.2422)\" font-family=\"'MyriadPro-Regular'\" font-size=\"25\"\u003eUnwanted Cells\u003c/text\u003e\n\u003ctext transform=\"matrix(1 0 0 1 12.8271 173.3662)\" font-family=\"'MyriadPro-Regular'\" font-size=\"25\"\u003eCD+ Cells\u003c/text\u003e\n\u003cg\u003e\n\t\u003cpolygon stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"158.353,205.239 150.153,202.41 156.704,194.296 \t\"/\u003e\n\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-width=\"3\" stroke-miterlimit=\"10\" x1=\"154.61\" y1=\"202.672\" x2=\"147.199\" y2=\"232.312\"/\u003e\n\u003c/g\u003e\n\u003cg\u003e\n\t\u003cpolygon stroke=\"#000000\" stroke-miterlimit=\"10\" points=\"135.032,139.835 133.309,134.386 142.02,130.715 \t\"/\u003e\n\t\n\t\t\u003cline fill=\"none\" stroke=\"#000000\" stroke-width=\"3\" stroke-miterlimit=\"10\" x1=\"135.147\" y1=\"136.361\" x2=\"110.832\" y2=\"156.343\"/\u003e\n\u003c/g\u003e\n\u003ctext transform=\"matrix(1 0 0 1 140.5879 38.501)\" font-family=\"'MyriadPro-Regular'\" font-size=\"41\"\u003e1\u003c/text\u003e\n\u003ctext transform=\"matrix(1 0 0 1 234.8652 36.8428)\" font-family=\"'MyriadPro-Regular'\" font-size=\"41\"\u003e2\u003c/text\u003e\n\u003ctext transform=\"matrix(1 0 0 1 326.0049 38.4541)\" font-family=\"'MyriadPro-Regular'\" font-size=\"41\"\u003e3\u003c/text\u003e\n\u003ctext transform=\"matrix(1 0 0 1 435.0049 38.4541)\" font-family=\"'MyriadPro-Regular'\" text-anchor=\"middle\" font-size=\"25\"\u003eCD4+/RBC\u003c/text\u003e\nEOF\n )\n img\n end\nend"}}]}