コード例 #1
0
    def _get_text_size(self, text, padding=2, file_format='pdf'):
        try:
            import cairocffi as cairo
        except ImportError:
            import cairo

        # Use dummy surface to determine text extents
        surface = create_new_surface(file_format)
        cr = cairo.Context(surface)
        cr.set_font_size(self.options['fontSizeNormal'])
        extents = cr.text_extents(text)
        width = extents[2] + 2 * padding
        height = extents[3] + 2 * padding
        return [0, 0, width, height]
コード例 #2
0
    def draw(self, reaction, file_format, path=None):
        """
        Draw the potential energy surface for the given `network` as a Cairo
        surface of the given `file_format`. If `path` is given, the surface is
        saved to that location on disk.
        """
        try:
            import cairocffi as cairo
        except ImportError:
            try:
                import cairo
            except ImportError:
                logging.warning(
                    'Cairo not found; potential energy surface will not be drawn.'
                )
                return

        self.reaction = reaction
        self.wells = [
            Well(self.reaction.reactants),
            Well(self.reaction.products)
        ]

        # Generate the bounding rectangles for each configuration label
        label_rects = []
        for well in self.wells:
            label_rects.append(
                self._get_label_size(well, file_format=file_format))

        # Get energy range (use kJ/mol internally)
        e0_min, e0_max = self._get_energy_range()
        e0_min *= 0.001
        e0_max *= 0.001

        # Drawing parameters
        padding = self.options['padding']
        well_width = self.options['wellWidth']
        well_spacing = self.options['wellSpacing']
        e_slope = self.options['Eslope']
        ts_width = self.options['TSwidth']

        e0_offset = self.options['E0offset'] * 0.001

        # Choose multiplier to convert energies to desired units (on figure only)
        e_units = self.options['Eunits']
        try:
            e_mult = {
                'J/mol': 1.0,
                'kJ/mol': 0.001,
                'cal/mol': 1.0 / 4.184,
                'kcal/mol': 1.0 / 4184.,
                'cm^-1': 1.0 / 11.962
            }[e_units]
        except KeyError:
            raise InputError(
                'Invalid value "{0}" for Eunits parameter.'.format(e_units))

        # Determine height required for drawing
        e_height = self._get_text_size('0.0', file_format=file_format)[3] + 6
        y_e0 = (e0_max - 0.0) * e_slope + padding + e_height
        height = (e0_max - e0_min) * e_slope + 2 * padding + e_height + 6
        for i in range(len(self.wells)):
            if 0.001 * self.wells[i].E0 == e0_min:
                height += label_rects[i][3]
                break

        # Determine naive position of each well (one per column)
        coordinates = np.zeros((len(self.wells), 2), np.float64)
        x = padding
        for i in range(len(self.wells)):
            well = self.wells[i]
            rect = label_rects[i]
            this_well_width = max(well_width, rect[2])
            e0 = 0.001 * well.E0
            y = y_e0 - e0 * e_slope
            coordinates[i] = [x + 0.5 * this_well_width, y]
            x += this_well_width + well_spacing
        width = x + padding - well_spacing

        # Determine the rectangles taken up by each well
        # We'll use this to merge columns safely so that wells don't overlap
        well_rects = []
        for i in range(len(self.wells)):
            l, t, w, h = label_rects[i]
            x, y = coordinates[i, :]
            if w < well_width:
                w = well_width
            t -= 6 + e_height
            h += 6 + e_height
            well_rects.append([l + x - 0.5 * w, t + y + 6, w, h])

        # Squish columns together from the left where possible until an isomer is encountered
        old_left = np.min(coordinates[:, 0])
        n_left = -1
        columns = []
        for i in range(n_left, -1, -1):
            top = well_rects[i][1]
            bottom = top + well_rects[i][3]
            for column in columns:
                for c in column:
                    top0 = well_rects[c][1]
                    bottom0 = top + well_rects[c][3]
                    if (top0 <= top <= bottom0) or (top <= top0 <= bottom):
                        # Can't put it in this column
                        break
                else:
                    # Can put it in this column
                    column.append(i)
                    break
            else:
                # Needs a new column
                columns.append([i])
        for column in columns:
            column_width = max([well_rects[c][2] for c in column])
            x = coordinates[column[0] + 1, 0] - 0.5 * well_rects[
                column[0] + 1][2] - well_spacing - 0.5 * column_width
            for c in column:
                delta = x - coordinates[c, 0]
                well_rects[c][0] += delta
                coordinates[c, 0] += delta
        new_left = np.min(coordinates[:, 0])
        coordinates[:, 0] -= new_left - old_left

        # Squish columns together from the right where possible until an isomer is encountered
        n_right = 3
        columns = []
        for i in range(n_right, len(self.wells)):
            top = well_rects[i][1]
            bottom = top + well_rects[i][3]
            for column in columns:
                for c in column:
                    top0 = well_rects[c][1]
                    bottom0 = top0 + well_rects[c][3]
                    if (top0 <= top <= bottom0) or (top <= top0 <= bottom):
                        # Can't put it in this column
                        break
                else:
                    # Can put it in this column
                    column.append(i)
                    break
            else:
                # Needs a new column
                columns.append([i])
        for column in columns:
            column_width = max([well_rects[c][2] for c in column])
            x = coordinates[column[0] - 1, 0] + 0.5 * well_rects[
                column[0] - 1][2] + well_spacing + 0.5 * column_width
            for c in column:
                delta = x - coordinates[c, 0]
                well_rects[c][0] += delta
                coordinates[c, 0] += delta

        width = max([rect[2] + rect[0] for rect in well_rects]) - min(
            [rect[0] for rect in well_rects]) + 2 * padding

        # Draw to the final surface
        surface = create_new_surface(file_format=file_format,
                                     target=path,
                                     width=width,
                                     height=height)
        cr = cairo.Context(surface)

        # Some global settings
        cr.select_font_face("sans")
        cr.set_font_size(self.options['fontSizeNormal'])

        # Fill the background with white
        cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
        cr.paint()
        self._draw_text('E0 ({0})'.format(e_units), cr, 15, 10,
                        padding=2)  # write units

        # Draw reactions
        e0_reac = self.wells[0].E0 * 0.001 - e0_offset
        e0_prod = self.wells[1].E0 * 0.001 - e0_offset
        e0_ts = self.reaction.transition_state.conformer.E0.value_si * 0.001 - e0_offset
        x1, y1 = coordinates[0, :]
        x2, y2 = coordinates[1, :]
        x1 += well_spacing / 2.0
        x2 -= well_spacing / 2.0
        if abs(e0_ts - e0_reac) > 0.1 and abs(e0_ts - e0_prod) > 0.1:
            if len(self.reaction.reactants) == 2:
                if e0_reac < e0_prod:
                    x0 = x1 + well_spacing * 0.5
                else:
                    x0 = x2 - well_spacing * 0.5
            elif len(self.reaction.products) == 2:
                if e0_reac < e0_prod:
                    x0 = x2 - well_spacing * 0.5
                else:
                    x0 = x1 + well_spacing * 0.5
            else:
                x0 = 0.5 * (x1 + x2)
            y0 = y_e0 - (e0_ts + e0_offset) * e_slope
            width1 = (x0 - x1)
            width2 = (x2 - x0)
            # Draw horizontal line for TS
            cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
            cr.set_line_width(2.0)
            cr.move_to(x0 - ts_width / 2.0, y0)
            cr.line_to(x0 + ts_width / 2.0, y0)
            cr.stroke()
            # Add background and text for energy
            e0 = "{0:.1f}".format(e0_ts * 1000. * e_mult)
            extents = cr.text_extents(e0)
            x = x0 - extents[2] / 2.0
            y = y0 - 6.0
            cr.rectangle(x + extents[0] - 2.0, y + extents[1] - 2.0,
                         extents[2] + 4.0, extents[3] + 4.0)
            cr.set_source_rgba(1.0, 1.0, 1.0, 0.75)
            cr.fill()
            cr.move_to(x, y)
            cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
            cr.show_text(e0)
            # Draw Bezier curve connecting reactants and products through TS
            cr.set_source_rgba(0.0, 0.0, 0.0, 0.5)
            cr.set_line_width(1.0)
            cr.move_to(x1, y1)
            cr.curve_to(x1 + width1 / 8.0, y1,
                        x0 - width1 / 8.0 - ts_width / 2.0, y0,
                        x0 - ts_width / 2.0, y0)
            cr.move_to(x0 + ts_width / 2.0, y0)
            cr.curve_to(x0 + width2 / 8.0 + ts_width / 2.0, y0,
                        x2 - width2 / 8.0, y2, x2, y2)
            cr.stroke()
        else:
            width = (x2 - x1)
            # Draw Bezier curve connecting reactants and products through TS
            cr.set_source_rgba(0.0, 0.0, 0.0, 0.5)
            cr.set_line_width(1.0)
            cr.move_to(x1, y1)
            cr.curve_to(x1 + width / 4.0, y1, x2 - width / 4.0, y2, x2, y2)
            cr.stroke()

        # Draw wells (after path reactions so that they are on top)
        for i, well in enumerate(self.wells):
            x0, y0 = coordinates[i, :]
            # Draw horizontal line for well
            cr.set_line_width(4.0)
            cr.move_to(x0 - well_width / 2.0, y0)
            cr.line_to(x0 + well_width / 2.0, y0)
            cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
            cr.stroke()
            # Add background and text for energy
            e0 = well.E0 * 0.001 - e0_offset
            e0 = "{0:.1f}".format(e0 * 1000. * e_mult)
            extents = cr.text_extents(e0)
            x = x0 - extents[2] / 2.0
            y = y0 - 6.0
            cr.rectangle(x + extents[0] - 2.0, y + extents[1] - 2.0,
                         extents[2] + 4.0, extents[3] + 4.0)
            cr.set_source_rgba(1.0, 1.0, 1.0, 0.75)
            cr.fill()
            cr.move_to(x, y)
            cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
            cr.show_text(e0)
            # Draw background and text for label
            x = x0 - 0.5 * label_rects[i][2]
            y = y0 + 6
            cr.rectangle(x, y, label_rects[i][2], label_rects[i][3])
            cr.set_source_rgba(1.0, 1.0, 1.0, 0.75)
            cr.fill()
            self._draw_label(well, cr, x, y, file_format=file_format)

        # Finish Cairo drawing
        if file_format == 'png':
            surface.write_to_png(path)
        else:
            surface.finish()