reaction_composer.rb 3.97 KB
Newer Older
Florian Hübsch's avatar
foo    
Florian Hübsch committed
1
require 'nokogiri'
Marco Sehrer's avatar
Marco Sehrer committed
2
3
4
5
6
require 'digest'

module SVG
  class ReactionComposer

7
    def initialize(materialsInchikeys, options = {})
Marco Sehrer's avatar
Marco Sehrer committed
8
      @svg_path = File.join(File.dirname(__FILE__), '..', '..', 'public', 'images', 'molecules')
9
10
11
      @starting_materials = materialsInchikeys[:starting_materials]
      @reactants = materialsInchikeys[:reactants]
      @products = materialsInchikeys[:products]
12
      number_of_reactants = (@reactants.size == 0 && @starting_materials.size != 0) ? 1 : @reactants.size
13
14
      @arrow_width = number_of_reactants * 50 + 60
      width = (@starting_materials.size + @products.size) * 100 + @arrow_width
15
16
      @label = options[:label]

Marco Sehrer's avatar
Marco Sehrer committed
17
      @template = <<-END
18
        <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:cml="http://www.xml-cml.org/schema"
19
          width="#{2*width}px" height="200px" viewBox="0 0 #{width} 100">
Marco Sehrer's avatar
Marco Sehrer committed
20
21
        <title>Reaction 1</title>
      END
Jan-Philipp Willem's avatar
Jan-Philipp Willem committed
22
23
      @labels = <<-END
        <svg font-family="sans-serif" font-size="8">
24
          <text text-anchor="middle" x="#{@arrow_width / 2}" y="65">#{@label}</text>
Marco Sehrer's avatar
Marco Sehrer committed
25
26
        </svg>
      END
Jan-Philipp Willem's avatar
Jan-Philipp Willem committed
27
28
29
30
31
      @divider = <<-END
      <svg font-family="sans-serif" font-size="14">
          <text x="0" y="50">+</text>
      </svg>
      END
Marco Sehrer's avatar
Marco Sehrer committed
32
      @arrow = <<-END
Jan-Philipp Willem's avatar
Jan-Philipp Willem committed
33
        <svg stroke="black" stroke-width="1">
34
35
          <line x1="0" y1="50" x2="#{@arrow_width}" y2="50"/>
          <polygon points="#{@arrow_width - 8},50 #{@arrow_width - 10},47 #{@arrow_width},50 #{@arrow_width - 10},53"/>
Marco Sehrer's avatar
Marco Sehrer committed
36
37
38
39
        </svg>
      END
    end

40
41
    def compose_reaction_svg_and_save(options = {})
      prefix = (options[:temp]) ? "temp-" : ""
Marco Sehrer's avatar
Marco Sehrer committed
42
      svg = compose_reaction_svg
43
      file_name = prefix + generate_filename
44
45
      File.open(file_path + "/" + file_name, 'w') { |file| file.write(svg) }
      file_name
Marco Sehrer's avatar
Marco Sehrer committed
46
47
48
    end

    def compose_reaction_svg
49
      @template.strip + sections.values.flatten.map(&:strip).join + "</svg>"
Marco Sehrer's avatar
Marco Sehrer committed
50
51
    end

Marco Sehrer's avatar
Marco Sehrer committed
52
    def file_path
53
      File.join(File.dirname(__FILE__), '..', '..', 'public', 'images', 'reactions')
Marco Sehrer's avatar
Marco Sehrer committed
54
55
    end

Marco Sehrer's avatar
Marco Sehrer committed
56
57
58
59
60
61
62
63
    private

      def innerFileContent fileName
        file = File.join(@svg_path, fileName + '.svg')
        doc = Nokogiri::XML(File.open(file))
        doc.css("g svg")
      end

64
65
66
67
68
69
      def compose_material_group( material_group, options = {} )
        shift = options[:start_at]
        material_width = options[:material_width]
        scale = options[:scale] || 1
        divider = ''
        material_group.map do |material|
Marco Sehrer's avatar
Marco Sehrer committed
70
          content = innerFileContent(material).to_s
71
72
73
          output = "<g transform='translate(#{shift}, 0) scale(#{scale})'>" + content + "</g>" + divider
          divider = "<g transform='translate(#{shift + material_width}, 0) scale(#{scale})'>" + @divider + "</g>"
          shift += material_width
Marco Sehrer's avatar
Marco Sehrer committed
74
75
          output
        end
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
      end

      def compose_arrow_and_reaction_labels( options = {} )
        shift = options[:start_at]
        output = "<g transform='translate(#{shift}, 0)'>" + @arrow + "</g>"
        output += "<g transform='translate(#{shift}, 0)'>" + @labels + "</g>"
        output
      end

      def sections
        sections = {}
        starting_materials_length = @starting_materials.length * 100
        sections[:starting_materials] = compose_material_group @starting_materials, {start_at: 0, material_width: 100}
        sections[:reactants] = compose_material_group @reactants, start_at: starting_materials_length + 30, material_width: 50, scale: 0.5
        sections[:arrow] = compose_arrow_and_reaction_labels start_at: starting_materials_length
        sections[:products] = compose_material_group @products, start_at: starting_materials_length + @arrow_width, material_width: 100
        sections
Marco Sehrer's avatar
Marco Sehrer committed
93
94
      end

95
      def generate_filename
Marco Sehrer's avatar
Marco Sehrer committed
96
        inchikeys = {:starting_materials => @starting_materials, :reactants => @reactants, :products => @products}
97
98
        key_base = "#{inchikeys.to_a.flatten.join}#{@label}"
        hash_of_inchikeys = Digest::SHA256.hexdigest(key_base)
Marco Sehrer's avatar
Marco Sehrer committed
99
100
101
102
103
104
        hash_of_inchikeys + '.svg'
      end


  end
end