Exemple #1
0
 def _draw_colored_line( self, start, end, line_width=1, capstyle=cairo.LINE_CAP_BUTT,
                         start_color=(0,0,0), end_color=(0,0,0)):
   x1,y1 = start
   x2,y2 = end
   length = geometry.point_distance( x1,y1,x2,y2)
   xn2,yn2 = geometry.elongate_line( x1,y1,x2,y2, -0.5*length)
   line1 = [(x1,y1),(xn2,yn2)]
   xn1,yn1 = geometry.elongate_line( x2,y2,x1,y1, -0.5*length)
   line2 = [(xn1,yn1),(x2,y2)]
   self.context.set_line_cap( cairo.LINE_CAP_BUTT) # this is forced here
   self.context.set_line_width( line_width)
   for line,color in zip( [line1,line2], [start_color,end_color]):
     self.context.set_source_rgb( *color)
     self._create_cairo_path( line, closed=False)
     self.context.stroke()
Exemple #2
0
 def _draw_colored_line(self,
                        start,
                        end,
                        line_width=1,
                        capstyle=cairo.LINE_CAP_BUTT,
                        start_color=(0, 0, 0),
                        end_color=(0, 0, 0)):
     x1, y1 = start
     x2, y2 = end
     length = geometry.point_distance(x1, y1, x2, y2)
     xn2, yn2 = geometry.elongate_line(x1, y1, x2, y2, -0.5 * length)
     line1 = [(x1, y1), (xn2, yn2)]
     xn1, yn1 = geometry.elongate_line(x2, y2, x1, y1, -0.5 * length)
     line2 = [(xn1, yn1), (x2, y2)]
     self.context.set_line_cap(cairo.LINE_CAP_BUTT)  # this is forced here
     self.context.set_line_width(line_width)
     for line, color in zip([line1, line2], [start_color, end_color]):
         self.context.set_source_rgb(*color)
         self._create_cairo_path(line, closed=False)
         self.context.stroke()
Exemple #3
0
  def _draw_vertex( self, v):
    pos = sum( [(a.x < v.x) and -1 or 1 for a in v.neighbors if abs(a.x-v.x)>0.2])
    if 'show_symbol' in v.properties_:
      show_symbol = v.properties_['show_symbol']
    else:
      show_symbol = (v.symbol != "C" or v.degree == 0 or self.show_carbon_symbol)
    if show_symbol:
      x = v.x
      y = v.y
      text = v.symbol
      hs = ""
      if self.show_hydrogens_on_hetero or v.properties_.get( 'show_hydrogens', False):
        if v.free_valency == 1:
          hs = "H"
        elif v.free_valency > 1:
          hs = "H<sub>%d</sub>" % v.free_valency
      if not hs:
        pos = -1
      if pos <= 0:
        text += hs
      else:
        text = hs + text
      # charge
      charge = ""
      if v.charge == 1:
        charge = "<sup>+</sup>"
      elif v.charge == -1:
        charge = "<sup>&#x2212;</sup>"
      elif v.charge > 1:
        charge = "<sup>%d+</sup>" % v.charge
      elif v.charge < -1:
        charge = "<sup>%d&#x2212;</sup>" % abs( v.charge)
      if charge:
        if self._is_there_place( v, v.x+3, v.y-2) or v.charge < 0:
          # we place negative charge regardless of available place
          # otherwise minus might be mistaken for a bond
          text += charge
          charge = ""
      
      # coloring
      if self.color_atoms:
        color = self.atom_colors.get( v.symbol, (0,0,0))
      else:
        color = (0,0,0)
      center_letter = pos <= 0 and 'first' or 'last'
      bbox = self._draw_text( (x,y), text, center_letter=center_letter, color=color)
      self._vertex_to_bbox[v] = ((x,y), geometry.expand_rectangle( bbox, self.space_around_atom))

      # sometimes charge is done here, if it wasn't done before
      if charge:
        assert v.charge > 0
        # if charge was not dealt with we change its appearance from 2+ to ++
        charge = v.charge * "+"
        if self._is_there_place( v, v.x, v.y-10):
          angle = 1.5*math.pi
        elif self._is_there_place( v, v.x, v.y+10):
          angle = 0.5*math.pi
        else:
          angle = self._find_place_around_atom( v)
        self.context.set_font_size( self.subscript_size_ratio * self.font_size)
        xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents( charge)
        x0 = v.x + 40*math.cos( angle)
        y0 = v.y + 40*math.sin( angle)
        line = (v.x,v.y,x0,y0)
        charge_bbox = [x0-0.5*width,y0-0.5*height,x0+0.5*width,y0+0.5*height]
        x1, y1 = geometry.intersection_of_line_and_rect( line, bbox, round_edges=0)
        x2, y2 = geometry.intersection_of_line_and_rect( line, charge_bbox, round_edges=0)
        x2, y2 = geometry.elongate_line( x1,y1,x2,y2, -self.space_around_atom)
        x = x0 + x1 - x2
        y = y0 + y1 - y2
        # draw
        self.context.set_source_rgb( *color)
        self.context.move_to( round( x-xbearing-0.5*width), round( y+0.5*height))
        self.context.show_text( charge)
Exemple #4
0
  def _draw_edge( self, e):
    def draw_plain_or_colored_line( _start, _end, second=False):
      """second means if this is not the main line, drawing might be different"""
      if not has_shown_vertex or not self.color_bonds:
        if not second:
          self._draw_line( _start, _end, line_width=self.line_width, capstyle=cairo.LINE_CAP_ROUND)
        else:
          self._draw_line( _start, _end, line_width=self.line_width, capstyle=cairo.LINE_CAP_BUTT)
      else:
        self._draw_colored_line( _start, _end, line_width=self.line_width, start_color=color1, end_color=color2)

    def draw_plain_or_colored_wedge( _start, _end):
      x1, y1 = _start
      x2, y2 = _end
      x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.wedge_width/2.0)
      xa, ya, xb, yb = geometry.find_parallel( x1, y1, x2, y2, self.line_width/2.0) 
      # no coloring now
      if not has_shown_vertex or not self.color_bonds:
        self._create_cairo_path( [(xa, ya), (x0, y0), (2*x2-x0, 2*y2-y0), (2*x1-xa, 2*y1-ya)], closed=True)
        self.context.set_source_rgb( 0,0,0)
        self.context.fill()
      else:
        # ratio 0.4 looks better than 0.5 because the area difference
        # is percieved more than length difference
        ratio = 0.4
        xm1 = ratio*xa + (1-ratio)*x0 
        ym1 = ratio*ya + (1-ratio)*y0
        xm2 = (1-ratio)*(2*x2-x0) + ratio*(2*x1-xa)
        ym2 = (1-ratio)*(2*y2-y0) + ratio*(2*y1-ya)
        self.context.set_source_rgb( *color1)
        self._create_cairo_path( [(xa,ya), (xm1,ym1), (xm2,ym2), (2*x1-xa, 2*y1-ya)], closed=True)
        self.context.fill()
        self.context.set_source_rgb( *color2)
        self._create_cairo_path( [(xm1,ym1), (x0, y0), (2*x2-x0, 2*y2-y0), (xm2,ym2)], closed=True)
        self.context.fill()

    def draw_plain_or_colored_hatch( _start, _end):
      x1, y1 = _start
      x2, y2 = _end
      # no coloring now
      x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.wedge_width/2.0)
      xa, ya, xb, yb = geometry.find_parallel( x1, y1, x2, y2, self.line_width/2.0) 
      d = math.sqrt( (x1-x2)**2 + (y1-y2)**2) # length of the bond
      if d == 0:  
        return  # to prevent division by zero
      dx1 = (x0 - xa)/d 
      dy1 = (y0 - ya)/d 
      dx2 = (2*x2 -x0 -2*x1 +xa)/d 
      dy2 = (2*y2 -y0 -2*y1 +ya)/d 
      # we have to decide if the first line should be at the position of the first atom
      draw_start = 1  # is index not boolean
      if not v1 in self._vertex_to_bbox and v1.occupied_valency > 1:
        draw_start = 1
      draw_end = 1    # is added to index not boolean
      if not v2 in self._vertex_to_bbox and v2.occupied_valency > 1:
        draw_end = 0
      # adjust the step length
      step_size = 2*(self.line_width)
      ns = round( d / step_size) or 1
      step_size = d / ns
      # now we finally draw
      self.context.set_line_cap( cairo.LINE_CAP_BUTT)
      self.context.set_source_rgb( *color1)
      middle = 0.5 * (draw_start + int( round( d/ step_size)) + draw_end - 2)
      for i in range( draw_start, int( round( d/ step_size)) +draw_end):
        coords = [xa+dx1*i*step_size, ya+dy1*i*step_size, 2*x1-xa+dx2*i*step_size, 2*y1-ya+dy2*i*step_size] 
        if coords[0] == coords[2] and coords[1] == coords[3]:
          if (dx1+dx2) > (dy1+dy2): 
            coords[0] += 1
          else:
            coords[1] += 1
        self._create_cairo_path( [coords[:2],coords[2:]])
        if i >= middle:
          self.context.stroke()
          self.context.set_source_rgb( *color2)
      self.context.stroke()

    # code itself
    # at first detect the need to make 3D adjustments
    self._transform = transform3d.transform3d()
    self._invtransform = transform3d.transform3d()
    transform = None
    if e.order > 1:
      atom1,atom2 = e.vertices
      for n in atom1.neighbors + atom2.neighbors:
        # e.atom1 and e.atom2 are in this list as well
        if n.z != 0:
          # engage 3d transform prior to detection of where to draw
          transform = self._get_3dtransform_for_drawing( e)
          #transform = None
          break
      if transform:
        for n in atom1.neighbors + atom2.neighbors:
          n.coords = transform.transform_xyz( *n.coords)
        self._transform = transform
        self._invtransform = transform.get_inverse()
    # // end of 3D adjustments
    # now the code itself
    coords = self._where_to_draw_from_and_to( e)
    if coords:
      start = coords[:2]
      end = coords[2:]
      v1, v2 = e.vertices
      if self.color_bonds:
        color1 = self.atom_colors.get( v1.symbol, (0,0,0))
        color2 = self.atom_colors.get( v2.symbol, (0,0,0))
      else:
        color1 = color2 = (0,0,0)
      has_shown_vertex = bool( [1 for _v in e.vertices if _v in self._vertex_to_bbox])

      if e.order == 1:
        if e.type == 'w':
          draw_plain_or_colored_wedge( start, end)
        elif e.type == 'h':
          draw_plain_or_colored_hatch( start, end)
        else:
          draw_plain_or_colored_line( start, end)

      if e.order == 2:
        side = 0
        # find how to center the bonds
        # rings have higher priority in setting the positioning
        in_ring = False
        for ring in self.molecule.get_smallest_independent_cycles_dangerous_and_cached():
          double_bonds = len( [b for b in self.molecule.vertex_subgraph_to_edge_subgraph(ring) if b.order == 2])
          if v1 in ring and v2 in ring:
            in_ring = True
            side += double_bonds * reduce( operator.add, [geometry.on_which_side_is_point( start+end, (a.x,a.y)) for a in ring if a!=v1 and a!=v2])
        # if rings did not decide, use the other neigbors
        if not side:
          for v in v1.neighbors + v2.neighbors:
            if v != v1 and v!= v2:
              side += geometry.on_which_side_is_point( start+end, (v.x, v.y))
        # if neighbors did not decide either
        if not side and (in_ring or not has_shown_vertex):
          if in_ring:
            # we don't want centered bonds inside rings
            side = 1 # select arbitrary value
          else:
            # bond between two unshown atoms - we want to center them only in some cases
            if len( v1.neighbors) == 1 and len( v2.neighbors) == 1:
              # both atoms have only one neighbor 
              side = 0
            elif len( v1.neighbors) < 3 and len( v2.neighbors) < 3:
              # try to figure out which side is more towards the center of the molecule
              side = reduce( operator.add, [geometry.on_which_side_is_point( start+end, (a.x,a.y))
                                            for a in self.molecule.vertices if a!=v1 and a!=v2], 0)
              if not side:
                side = 1 # we choose arbitrary value, we don't want centering
        if side:
          draw_plain_or_colored_line( start, end)
          x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], self.bond_width*misc.signum( side))
          # shorten the second line
          length = geometry.point_distance( x1,y1,x2,y2)
          if v2 not in self._vertex_to_bbox:
            x2, y2 = geometry.elongate_line( x1, y1, x2, y2, -self.bond_second_line_shortening*length)
          if v1 not in self._vertex_to_bbox:
            x1, y1 = geometry.elongate_line( x2, y2, x1, y1, -self.bond_second_line_shortening*length)
          draw_plain_or_colored_line( (x1, y1), (x2, y2), second=True)
        else:
          for i in (1,-1):
            x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.5)
            draw_plain_or_colored_line( (x1, y1), (x2, y2))

      elif e.order == 3:
        self._draw_line( start, end, line_width=self.line_width)
        for i in (1,-1):
          x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.7)
          draw_plain_or_colored_line( (x1, y1), (x2, y2), second=True)

    if transform:
      # if transform was used, we need to transform back
      for n in atom1.neighbors + atom2.neighbors:
        n.coords = self._invtransform.transform_xyz( *n.coords)
Exemple #5
0
    def _draw_vertex(self, v):
        pos = sum([(a.x < v.x) and -1 or 1 for a in v.neighbors
                   if abs(a.x - v.x) > 0.2])
        if 'show_symbol' in v.properties_:
            show_symbol = v.properties_['show_symbol']
        else:
            show_symbol = (v.symbol != "C" or v.degree == 0
                           or self.show_carbon_symbol)
        if show_symbol:
            x = v.x
            y = v.y
            text = v.symbol
            hs = ""
            if self.show_hydrogens_on_hetero or v.properties_.get(
                    'show_hydrogens', False):
                if v.free_valency == 1:
                    hs = "H"
                elif v.free_valency > 1:
                    hs = "H<sub>%d</sub>" % v.free_valency
            if not hs:
                pos = -1
            if pos <= 0:
                text += hs
            else:
                text = hs + text
            # charge
            charge = ""
            if v.charge == 1:
                charge = "<sup>+</sup>"
            elif v.charge == -1:
                charge = "<sup>&#x2212;</sup>"
            elif v.charge > 1:
                charge = "<sup>%d+</sup>" % v.charge
            elif v.charge < -1:
                charge = "<sup>%d&#x2212;</sup>" % abs(v.charge)
            if charge:
                if self._is_there_place(v, v.x + 3, v.y - 2) or v.charge < 0:
                    # we place negative charge regardless of available place
                    # otherwise minus might be mistaken for a bond
                    text += charge
                    charge = ""

            # coloring
            if self.color_atoms:
                color = self.atom_colors.get(v.symbol, (0, 0, 0))
            else:
                color = (0, 0, 0)
            center_letter = pos <= 0 and 'first' or 'last'
            bbox = self._draw_text((x, y),
                                   text,
                                   center_letter=center_letter,
                                   color=color)
            self._vertex_to_bbox[v] = ((x, y),
                                       geometry.expand_rectangle(
                                           bbox, self.space_around_atom))

            # sometimes charge is done here, if it wasn't done before
            if charge:
                assert v.charge > 0
                # if charge was not dealt with we change its appearance from 2+ to ++
                charge = v.charge * "+"
                if self._is_there_place(v, v.x, v.y - 10):
                    angle = 1.5 * math.pi
                elif self._is_there_place(v, v.x, v.y + 10):
                    angle = 0.5 * math.pi
                else:
                    angle = self._find_place_around_atom(v)
                self.context.set_font_size(self.subscript_size_ratio *
                                           self.font_size)
                xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents(
                    charge)
                x0 = v.x + 40 * math.cos(angle)
                y0 = v.y + 40 * math.sin(angle)
                line = (v.x, v.y, x0, y0)
                charge_bbox = [
                    x0 - 0.5 * width, y0 - 0.5 * height, x0 + 0.5 * width,
                    y0 + 0.5 * height
                ]
                x1, y1 = geometry.intersection_of_line_and_rect(line,
                                                                bbox,
                                                                round_edges=0)
                x2, y2 = geometry.intersection_of_line_and_rect(line,
                                                                charge_bbox,
                                                                round_edges=0)
                x2, y2 = geometry.elongate_line(x1, y1, x2, y2,
                                                -self.space_around_atom)
                x = x0 + x1 - x2
                y = y0 + y1 - y2
                # draw
                self.context.set_source_rgb(*color)
                self.context.move_to(round(x - xbearing - 0.5 * width),
                                     round(y + 0.5 * height))
                self.context.show_text(charge)
Exemple #6
0
    def _draw_edge(self, e):
        def draw_plain_or_colored_line(_start, _end, second=False):
            """second means if this is not the main line, drawing might be different"""
            if not has_shown_vertex or not self.color_bonds:
                if not second:
                    self._draw_line(_start,
                                    _end,
                                    line_width=self.line_width,
                                    capstyle=cairo.LINE_CAP_ROUND)
                else:
                    self._draw_line(_start,
                                    _end,
                                    line_width=self.line_width,
                                    capstyle=cairo.LINE_CAP_BUTT)
            else:
                self._draw_colored_line(_start,
                                        _end,
                                        line_width=self.line_width,
                                        start_color=color1,
                                        end_color=color2)

        def draw_plain_or_colored_wedge(_start, _end):
            x1, y1 = _start
            x2, y2 = _end
            x, y, x0, y0 = geometry.find_parallel(x1, y1, x2, y2,
                                                  self.wedge_width / 2.0)
            xa, ya, xb, yb = geometry.find_parallel(x1, y1, x2, y2,
                                                    self.line_width / 2.0)
            # no coloring now
            if not has_shown_vertex or not self.color_bonds:
                self._create_cairo_path([(xa, ya), (x0, y0),
                                         (2 * x2 - x0, 2 * y2 - y0),
                                         (2 * x1 - xa, 2 * y1 - ya)],
                                        closed=True)
                self.context.set_source_rgb(0, 0, 0)
                self.context.fill()
            else:
                # ratio 0.4 looks better than 0.5 because the area difference
                # is percieved more than length difference
                ratio = 0.4
                xm1 = ratio * xa + (1 - ratio) * x0
                ym1 = ratio * ya + (1 - ratio) * y0
                xm2 = (1 - ratio) * (2 * x2 - x0) + ratio * (2 * x1 - xa)
                ym2 = (1 - ratio) * (2 * y2 - y0) + ratio * (2 * y1 - ya)
                self.context.set_source_rgb(*color1)
                self._create_cairo_path([(xa, ya), (xm1, ym1), (xm2, ym2),
                                         (2 * x1 - xa, 2 * y1 - ya)],
                                        closed=True)
                self.context.fill()
                self.context.set_source_rgb(*color2)
                self._create_cairo_path([(xm1, ym1), (x0, y0),
                                         (2 * x2 - x0, 2 * y2 - y0),
                                         (xm2, ym2)],
                                        closed=True)
                self.context.fill()

        def draw_plain_or_colored_hatch(_start, _end):
            x1, y1 = _start
            x2, y2 = _end
            # no coloring now
            x, y, x0, y0 = geometry.find_parallel(x1, y1, x2, y2,
                                                  self.wedge_width / 2.0)
            xa, ya, xb, yb = geometry.find_parallel(x1, y1, x2, y2,
                                                    self.line_width / 2.0)
            d = math.sqrt((x1 - x2)**2 + (y1 - y2)**2)  # length of the bond
            if d == 0:
                return  # to prevent division by zero
            dx1 = (x0 - xa) / d
            dy1 = (y0 - ya) / d
            dx2 = (2 * x2 - x0 - 2 * x1 + xa) / d
            dy2 = (2 * y2 - y0 - 2 * y1 + ya) / d
            # we have to decide if the first line should be at the position of the first atom
            draw_start = 1  # is index not boolean
            if not v1 in self._vertex_to_bbox and v1.occupied_valency > 1:
                draw_start = 1
            draw_end = 1  # is added to index not boolean
            if not v2 in self._vertex_to_bbox and v2.occupied_valency > 1:
                draw_end = 0
            # adjust the step length
            step_size = 2 * (self.line_width)
            ns = round(d / step_size) or 1
            step_size = d / ns
            # now we finally draw
            self.context.set_line_cap(cairo.LINE_CAP_BUTT)
            self.context.set_source_rgb(*color1)
            middle = 0.5 * (draw_start + int(round(d / step_size)) + draw_end -
                            2)
            for i in range(draw_start, int(round(d / step_size)) + draw_end):
                coords = [
                    xa + dx1 * i * step_size, ya + dy1 * i * step_size,
                    2 * x1 - xa + dx2 * i * step_size,
                    2 * y1 - ya + dy2 * i * step_size
                ]
                if coords[0] == coords[2] and coords[1] == coords[3]:
                    if (dx1 + dx2) > (dy1 + dy2):
                        coords[0] += 1
                    else:
                        coords[1] += 1
                self._create_cairo_path([coords[:2], coords[2:]])
                if i >= middle:
                    self.context.stroke()
                    self.context.set_source_rgb(*color2)
            self.context.stroke()

        # code itself
        # at first detect the need to make 3D adjustments
        self._transform = transform3d.transform3d()
        self._invtransform = transform3d.transform3d()
        transform = None
        if e.order > 1:
            atom1, atom2 = e.vertices
            for n in atom1.neighbors + atom2.neighbors:
                # e.atom1 and e.atom2 are in this list as well
                if n.z != 0:
                    # engage 3d transform prior to detection of where to draw
                    transform = self._get_3dtransform_for_drawing(e)
                    #transform = None
                    break
            if transform:
                for n in atom1.neighbors + atom2.neighbors:
                    n.coords = transform.transform_xyz(*n.coords)
                self._transform = transform
                self._invtransform = transform.get_inverse()
        # // end of 3D adjustments
        # now the code itself
        coords = self._where_to_draw_from_and_to(e)
        if coords:
            start = coords[:2]
            end = coords[2:]
            v1, v2 = e.vertices
            if self.color_bonds:
                color1 = self.atom_colors.get(v1.symbol, (0, 0, 0))
                color2 = self.atom_colors.get(v2.symbol, (0, 0, 0))
            else:
                color1 = color2 = (0, 0, 0)
            has_shown_vertex = bool(
                [1 for _v in e.vertices if _v in self._vertex_to_bbox])

            if e.order == 1:
                if e.type == 'w':
                    draw_plain_or_colored_wedge(start, end)
                elif e.type == 'h':
                    draw_plain_or_colored_hatch(start, end)
                else:
                    draw_plain_or_colored_line(start, end)

            if e.order == 2:
                side = 0
                # find how to center the bonds
                # rings have higher priority in setting the positioning
                in_ring = False
                for ring in self.molecule.get_smallest_independent_cycles_dangerous_and_cached(
                ):
                    double_bonds = len([
                        b for b in
                        self.molecule.vertex_subgraph_to_edge_subgraph(ring)
                        if b.order == 2
                    ])
                    if v1 in ring and v2 in ring:
                        in_ring = True
                        side += double_bonds * reduce(operator.add, [
                            geometry.on_which_side_is_point(
                                start + end, (a.x, a.y))
                            for a in ring if a != v1 and a != v2
                        ])
                # if rings did not decide, use the other neigbors
                if not side:
                    for v in v1.neighbors + v2.neighbors:
                        if v != v1 and v != v2:
                            side += geometry.on_which_side_is_point(
                                start + end, (v.x, v.y))
                # if neighbors did not decide either
                if not side and (in_ring or not has_shown_vertex):
                    if in_ring:
                        # we don't want centered bonds inside rings
                        side = 1  # select arbitrary value
                    else:
                        # bond between two unshown atoms - we want to center them only in some cases
                        if len(v1.neighbors) == 1 and len(v2.neighbors) == 1:
                            # both atoms have only one neighbor
                            side = 0
                        elif len(v1.neighbors) < 3 and len(v2.neighbors) < 3:
                            # try to figure out which side is more towards the center of the molecule
                            side = reduce(operator.add, [
                                geometry.on_which_side_is_point(
                                    start + end, (a.x, a.y))
                                for a in self.molecule.vertices
                                if a != v1 and a != v2
                            ], 0)
                            if not side:
                                side = 1  # we choose arbitrary value, we don't want centering
                if side:
                    draw_plain_or_colored_line(start, end)
                    x1, y1, x2, y2 = geometry.find_parallel(
                        start[0], start[1], end[0], end[1],
                        self.bond_width * misc.signum(side))
                    # shorten the second line
                    length = geometry.point_distance(x1, y1, x2, y2)
                    if v2 not in self._vertex_to_bbox:
                        x2, y2 = geometry.elongate_line(
                            x1, y1, x2, y2,
                            -self.bond_second_line_shortening * length)
                    if v1 not in self._vertex_to_bbox:
                        x1, y1 = geometry.elongate_line(
                            x2, y2, x1, y1,
                            -self.bond_second_line_shortening * length)
                    draw_plain_or_colored_line((x1, y1), (x2, y2), second=True)
                else:
                    for i in (1, -1):
                        x1, y1, x2, y2 = geometry.find_parallel(
                            start[0], start[1], end[0], end[1],
                            i * self.bond_width * 0.5)
                        draw_plain_or_colored_line((x1, y1), (x2, y2))

            elif e.order == 3:
                self._draw_line(start, end, line_width=self.line_width)
                for i in (1, -1):
                    x1, y1, x2, y2 = geometry.find_parallel(
                        start[0], start[1], end[0], end[1],
                        i * self.bond_width * 0.7)
                    draw_plain_or_colored_line((x1, y1), (x2, y2), second=True)

        if transform:
            # if transform was used, we need to transform back
            for n in atom1.neighbors + atom2.neighbors:
                n.coords = self._invtransform.transform_xyz(*n.coords)