Example #1
0
    def on_draw(self, widget: Gtk.Widget, cr: cairo.Context) -> bool:
        if self.frozen:
            return False

        if not self.row.valid():
            if self.sig is not None:
                self.tw.disconnect(self.sig)
                self.sig = None
            return False

        path = self.row.get_path()

        color = self.stylecontext.get_background_color(Gtk.StateFlags.NORMAL)

        if not self.columns:
            self.columns = self.tw.get_columns()
        assert self.columns is not None

        for col in self.columns:
            rect = self.tw.get_background_area(path, col)
            cr.rectangle(rect.x, rect.y, rect.width, rect.height)

        cr.clip()

        cr.set_source_rgba(color.red, color.green, color.blue,
                           1.0 - self.get_state())
        cr.set_operator(cairo.OPERATOR_OVER)
        cr.paint()

        return False
Example #2
0
    def on_draw(self, widget: Widget, context: cairo.Context):
        if not self.is_shape_set:
            self.layout(context)

        context.set_font_size(self.font_size)

        self.shape.draw_on_context(context)
        context.set_source_rgb(1, 1, 1)
        context.fill_preserve()
        context.set_source_rgb(0, 0, 0)
        context.stroke()
        shape = self.shape

        label = self.label
        if len(label) > 0 and label[-1] == ' ':
            label += '.'
        xb, yb, w, h, xa, ya = context.text_extents(label)
        context.rectangle(shape.start.x + self.padding,
                          shape.start.y,
                          shape.width - self.padding,
                          shape.height)
        context.clip()
        context.move_to(shape.start.x + (shape.width - self.padding - w)/2,
                        shape.start.y + shape.height - self.padding)
        context.show_text(self.label)
Example #3
0
 def create_surface(self):
     bw, bh = self.size
     old_backing = self._backing
     #should we honour self.depth here?
     self._backing = None
     if bw==0 or bh==0:
         #this can happen during cleanup
         return None
     backing = ImageSurface(FORMAT_ARGB32, bw, bh)
     self._backing = backing
     cr = Context(backing)
     if self._alpha_enabled:
         cr.set_operator(OPERATOR_CLEAR)
         cr.set_source_rgba(1, 1, 1, 0)
     else:
         cr.set_operator(OPERATOR_SOURCE)
         cr.set_source_rgba(1, 1, 1, 1)
     cr.rectangle(0, 0, bw, bh)
     cr.fill()
     if COPY_OLD_BACKING and old_backing is not None:
         oldw, oldh = old_backing.get_width(), old_backing.get_height()
         sx, sy, dx, dy, w, h = self.gravity_copy_coords(oldw, oldh, bw, bh)
         cr.translate(dx-sx, dy-sy)
         cr.rectangle(sx, sy, w, h)
         cr.clip()
         cr.set_operator(OPERATOR_SOURCE)
         cr.set_source_surface(old_backing, 0, 0)
         cr.paint()
         backing.flush()
     return cr
Example #4
0
    def cairo_paint_from_source(self, set_source_fn, source, x: int, y: int,
                                iw: int, ih: int, width: int, height: int,
                                options):
        """ must be called from UI thread """
        backing = self._backing
        log("cairo_paint_surface%s backing=%s, paint box line width=%i",
            (set_source_fn, source, x, y, iw, ih, width, height, options),
            backing, self.paint_box_line_width)
        if not backing:
            return
        gc = Context(backing)
        if self.paint_box_line_width:
            gc.save()

        gc.rectangle(x, y, width, height)
        gc.clip()

        gc.set_operator(OPERATOR_CLEAR)
        gc.rectangle(x, y, width, height)
        gc.fill()

        gc.set_operator(OPERATOR_SOURCE)
        gc.translate(x, y)
        if iw != width or ih != height:
            gc.scale(width / iw, height / ih)
        gc.rectangle(0, 0, width, height)
        set_source_fn(gc, source, 0, 0)
        gc.paint()

        if self.paint_box_line_width:
            gc.restore()
            encoding = options.get("encoding")
            self.cairo_paint_box(gc, encoding, x, y, width, height)
Example #5
0
def draw_crown(ctx: cairo.Context, pos_x: float, pos_y: float, radius: float):
    ctx.arc(pos_x, pos_y, radius, 0, tau)
    ctx.stroke_preserve()
    ctx.clip()

    crown_eclipse = -0.2

    eclipse_cy = pos_y + crown_eclipse * 2.0 * radius
    ctx.arc(pos_x, eclipse_cy, radius, 0, tau)
    ctx.fill()
    ctx.reset_clip()
Example #6
0
 def on_draw(self, widget: Widget, context: cairo.Context):
     for child in self.list:
         if child.visible:
             context.save()
             context.transform(child.fromWidgetCoords)
             if child.is_clip_set():
                 rectangle = child.clip_rectangle
                 context.rectangle(rectangle.start.x, rectangle.start.y,
                                   rectangle.width, rectangle.height)
                 context.clip()
             child.on_draw(self, context)
             context.restore()
Example #7
0
 def on_draw(self, widget: Widget, context: cairo.Context):
     for child in self.list:
         if child.visible:
             context.save()
             context.transform(child.fromWidgetCoords)
             if child.is_clip_set():
                 rectangle = child.clip_rectangle
                 context.rectangle(rectangle.start.x,rectangle.start.y,
                                   rectangle.width,rectangle.height)
                 context.clip()
             child.on_draw(self,context)
             context.restore()
Example #8
0
    def on_draw(self, _widget: Gtk.Widget, cr: cairo.Context) -> bool:
        if self.frozen:
            return False

        if not self.row.valid():
            if self.sig is not None:
                self.tw.disconnect(self.sig)
                self.sig = None

        assert self.tw.liststore is not None
        path = self.row.get_path()
        if path is None:
            return False

        path = self.tw.filter.convert_child_path_to_path(path)
        if path is None:
            return False

        # FIXME Use Gtk.render_background to render background.
        # However it does not use the correct colors/gradient.
        for col in self.columns:
            bg_rect = self.tw.get_background_area(path, col)
            rect = self.tw.get_cell_area(path, col)
            rect.y = bg_rect.y
            rect.height = bg_rect.height

            cr.rectangle(rect.x, rect.y, rect.width, rect.height)

        cr.clip()

        maybe_selected = self.tw.selected()
        if maybe_selected is not None:
            selected = self.tw.liststore.get_path(maybe_selected) == path
        else:
            selected = False

        stylecontext = self.tw.get_style_context()

        if selected:
            bg_color = stylecontext.get_background_color(
                Gtk.StateFlags.SELECTED)
        else:
            bg_color = stylecontext.get_background_color(Gtk.StateFlags.NORMAL)

        cr.set_source_rgb(bg_color.red, bg_color.green, bg_color.blue)
        cr.paint_with_alpha(1.0 - self.get_state())

        return False
Example #9
0
 def paint_image(self, img, x, y, w, h):
     #roundtrip via png (yuk)
     from io import BytesIO
     png = BytesIO()
     img.save(png, format="PNG")
     reader = BytesIO(png.getvalue())
     png.close()
     img = ImageSurface.create_from_png(reader)
     gc = Context(self.backing)
     gc.rectangle(x, y, w, h)
     gc.clip()
     gc.set_operator(OPERATOR_CLEAR)
     gc.rectangle(x, y, w, h)
     gc.fill()
     gc.set_operator(OPERATOR_SOURCE)
     gc.translate(x, y)
     gc.rectangle(0, 0, w, h)
     gc.set_source_surface(img, x, y)
     gc.paint()
Example #10
0
    def draw(self, cr: cairo.Context, drawing_options: DrawingOptions):
        if self.direction == CurveDirection.left:
            cy = -self.radius
            angle1, angle2 = math.pi / 2 - math.tau / self.per_circle, math.pi / 2
        else:
            cy = self.radius
            angle1, angle2 = -math.pi / 2, math.tau / self.per_circle - math.pi / 2

        cr.save()

        cr.set_source_rgb(0.9, 0.9, 0.9)
        cr.move_to(0, cy)
        cr.arc(0, cy, self.radius + 5, angle1, angle2)
        cr.line_to(0, cy)
        cr.clip()

        for i in range(0, self.sleepers + 1):
            angle = (math.tau / self.per_circle) * (i / self.sleepers)
            if self.direction == CurveDirection.left:
                angle = -math.pi / 2 - angle
            else:
                angle = angle + math.pi / 2
            cr.move_to(
                -math.cos(angle) * (self.radius - 4),
                cy - math.sin(angle) * (self.radius - 4),
            )
            cr.line_to(
                -math.cos(angle) * (self.radius + 4),
                cy - math.sin(angle) * (self.radius + 4),
            )

        cr.set_line_width(2)
        cr.set_source_rgb(*drawing_options.sleeper_color)
        cr.stroke()

        cr.set_line_width(1)
        cr.set_source_rgb(*drawing_options.rail_color)
        cr.arc(0, cy, self.radius - 2.5, angle1, angle2)
        cr.stroke()
        cr.arc(0, cy, self.radius + 2.5, angle1, angle2)
        cr.stroke()

        cr.restore()
Example #11
0
    def draw(self, ctx: cairo.Context, eye_radius: float, relative_to=(0, 0)):
        pos = self.pos - relative_to

        top_intersection = pos + np.array([0, self.opening / 2])
        bottom_intersection = pos - np.array([0, self.opening / 2])
        right_point = pos + np.array([self.size, 0])
        left_point = pos - np.array([self.size, 0])

        center_1, rad_1 = circle_from_3_points(left_point, top_intersection,
                                               right_point)
        center_2, rad_2 = circle_from_3_points(left_point, bottom_intersection,
                                               right_point)

        # Eyelid 1:
        ctx.push_group()
        with source(ctx, self.color.to_pattern()):
            # Restrict drawing area to eyeball:
            ctx.arc(pos[0], pos[1], eye_radius + 1, 0, math.tau)
            ctx.clip()
            ctx.paint()
            # Sub circle 1:
            with operator(ctx, cairo.Operator.CLEAR):
                ctx.arc(center_1[0], center_1[1], rad_1, 0, math.tau)
                ctx.fill()
            ctx.reset_clip()
        with source(ctx, ctx.pop_group()):
            ctx.paint()

        # Eyelid 2:
        ctx.push_group()
        with source(ctx, self.color.to_pattern()):
            # Restrict drawing area to eyeball:
            ctx.arc(pos[0], pos[1], eye_radius + 1, 0, math.tau)
            ctx.clip()
            ctx.paint()
            # Sub circle 2:
            with operator(ctx, cairo.Operator.CLEAR):
                ctx.arc(center_2[0], center_2[1], rad_2, 0, math.tau)
                ctx.fill()
            ctx.reset_clip()
        with source(ctx, ctx.pop_group()):
            ctx.paint()
Example #12
0
    def do_draw(self, cr: cairo.Context) -> None:
        viewport_widget_extents = self.props.viewport_widget_extents

        with cairo_saved(cr):
            cr.rectangle(*viewport_widget_extents.position,
                         *viewport_widget_extents.size)
            cr.clip()
            for ro in self._render_objects:
                ro.draw(cr)

        if self.has_focus():
            # Draw focus indicator
            stroke_width = 1
            rectangle_pos = viewport_widget_extents.position + (
                stroke_width / 2, stroke_width / 2)
            rectangle_size = viewport_widget_extents.size - (stroke_width,
                                                             stroke_width)
            cr.rectangle(*rectangle_pos, *rectangle_size)
            cr.set_source_rgb(70 / 255, 142 / 255, 220 / 255)
            cr.set_line_width(stroke_width)
            cr.stroke()
Example #13
0
    def draw(self, ctx: cairo.Context, relative_to=(0, 0)):
        pos = self.pos - relative_to

        top_intersection = pos + np.array([0, self.size])
        bottom_intersection = pos - np.array([0, self.size])
        right_edge = pos + np.array([self.width / 2, 0])
        left_edge = pos - np.array([self.width / 2, 0])

        center_1, rad_1 = circle_from_3_points(top_intersection, right_edge,
                                               bottom_intersection)
        center_2, rad_2 = circle_from_3_points(top_intersection, left_edge,
                                               bottom_intersection)

        ctx.arc(center_1[0], center_1[1], rad_1, 0, math.tau)
        ctx.clip()

        ctx.arc(center_2[0], center_2[1], rad_2, 0, math.tau)
        ctx.clip()

        ctx.paint()
        ctx.reset_clip()
Example #14
0
    def on_draw(self, widget: Widget, context: cairo.Context):
        if not self.is_shape_set:
            self.layout(context)

        context.set_font_size(self.font_size)

        self.shape.draw_on_context(context)
        context.set_source_rgb(1, 1, 1)
        context.fill_preserve()
        context.set_source_rgb(0, 0, 0)
        context.stroke()
        shape = self.shape

        label = self.label
        if len(label) > 0 and label[-1] == ' ':
            label += '.'
        xb, yb, w, h, xa, ya = context.text_extents(label)
        context.rectangle(shape.start.x + self.padding, shape.start.y,
                          shape.width - self.padding, shape.height)
        context.clip()
        context.move_to(shape.start.x + (shape.width - self.padding - w) / 2,
                        shape.start.y + shape.height - self.padding)
        context.show_text(self.label)
Example #15
0
    def on_draw(self, _widget: Gtk.Widget, cr: cairo.Context) -> bool:
        if self.frozen:
            return False

        if not self.row.valid():
            self.tw.disconnect(self.sig)
            self.sig = None

        path = self.row.get_path()

        # FIXME Use Gtk.render_background to render background.
        # However it does not use the correct colors/gradient.
        for col in self.columns:
            bg_rect = self.tw.get_background_area(path, col)
            rect = self.tw.get_cell_area(path, col)
            rect.y = bg_rect.y
            rect.height = bg_rect.height

            cr.rectangle(rect.x, rect.y, rect.width, rect.height)

        cr.clip()

        selected = self.selection.get_selected()[1] and \
            self.tw.props.model.get_path(self.selection.get_selected()[1]) == path

        stylecontext = self.tw.get_style_context()

        if selected:
            bg_color = stylecontext.get_background_color(
                Gtk.StateFlags.SELECTED)
        else:
            bg_color = stylecontext.get_background_color(Gtk.StateFlags.NORMAL)

        cr.set_source_rgb(bg_color.red, bg_color.green, bg_color.blue)
        cr.paint_with_alpha(1.0 - self.get_state())

        return False
Example #16
0
def draw_moon(
    ctx: cairo.Context,
    pos_x: float,
    pos_y: float,
    radius: float,
    eclipse_pct: float = -1.0,
):
    moon_color = Color(0.9, 0.9, 0.9)

    ctx.arc(pos_x, pos_y, radius, 0, tau)
    ctx.stroke_preserve()

    with source(
        ctx,
        moon_color.to_pattern(),
    ):
        ctx.fill_preserve()
        ctx.clip()

    with source(ctx, Color(0.0, 0.0, 0.0).to_pattern()):
        eclipse_cx = pos_x + eclipse_pct * 2.0 * radius
        ctx.arc(eclipse_cx, pos_y, radius, 0, tau)
        ctx.fill()
        ctx.reset_clip()
Example #17
0
class Canvas:
    def __init__(self,
                 width: int,
                 height: Optional[int] = None,
                 *,
                 normalise: bool = True):
        if height is None:
            height = width
        self._width = width
        self._height = height
        self._surface = ImageSurface(FORMAT_ARGB32, width, height)
        self._context = Context(self._surface)
        self._normalise = normalise
        if self._normalise:
            self.scale(self._width, self._height)

    @property
    def width(self) -> int:
        return self._width if not self._normalise else 1

    @property
    def height(self) -> int:
        return self._height if not self._normalise else 1

    # TODO depreciate
    @property
    def context(self) -> Context:  # pragma: no cover
        return self._context

    def save(self) -> None:
        self._context.save()

    def restore(self) -> None:
        self._context.restore()

    # Transformation
    def rotate(self, angle: float) -> None:
        self._context.rotate(angle)

    def translate(self, x: float, y: float) -> None:
        self._context.translate(x, y)

    def scale(self, width: int, height: int) -> None:
        self._context.scale(width, height)

    def set_line_width(self, width: float) -> None:
        self._context.set_line_width(width)

    # Colour functionality
    def set_colour(self, colour: Colour) -> None:
        self._context.set_source_rgba(colour.red, colour.blue, colour.green,
                                      colour.alpha)

    def set_grey(self, colour_value: float, alpha: float = 1) -> None:
        colour = Colour(*(colour_value, ) * 3, alpha)
        self.set_colour(colour)

    def set_black(self) -> None:
        self.set_colour(BLACK)

    def set_white(self) -> None:
        self.set_colour(WHITE)

    def set_background(self, colour: Colour) -> None:
        self._context.rectangle(0, 0, self.width, self.height)
        self.set_colour(colour)
        self._context.fill()

    def set_line_cap(self, line_cap: LineCap) -> None:
        self._context.set_line_cap(line_cap.value)

    def set_line_join(self, line_join: LineJoin) -> None:
        self._context.set_line_join(line_join.value)

    def set_fill_rule(self, file_rule: FillRule) -> None:
        self._context.set_fill_rule(file_rule.value)

    # Render methods
    def fill(self, preserve: bool = False) -> None:
        if not preserve:
            self._context.fill()
        else:
            self._context.fill_preserve()

    def stroke(self, preserve: bool = False) -> None:
        if not preserve:
            self._context.stroke()
        else:
            self._context.stroke_preserve()

    def clip(self) -> None:
        self._context.clip()

    def _draw_path(self, path: Iterable[Point], close_path: bool) -> None:
        self._context.new_sub_path()
        for p in path:
            self._context.line_to(*p)
        if close_path:
            self._context.close_path()

    def draw_path(self,
                  path: Iterable[PointType],
                  close_path: bool = False) -> None:
        points = (p if isinstance(p, Point) else Point(*p) for p in path)
        self._draw_path(points, close_path)

    def draw_polygon(self, polygon: Polygon) -> None:
        self._draw_path(polygon.points, close_path=True)

    def draw_circle(self,
                    circle: Circle,
                    fill: bool = False,
                    stroke: bool = True) -> None:
        self._context.new_sub_path()
        self._context.arc(*circle.centre, circle.radius, 0, 2 * pi)

    def write_to_png(self, file_name: str) -> None:
        self._surface.write_to_png(file_name)
Example #18
0
    def on_draw(self, widget: Widget, context: cairo.Context):
        self.min_size = 200, 150
        super().on_draw(widget, context)

        context.save()
        context.set_font_size(self.font_size)

        if self._table_extents is None:
            self._update_table_extents(context)

        skip = max(self.skip, 0)
        how_many = self.how_many

        table_extents = self._table_extents
        title_extents = self._table_extents.title_extents

        expected_height = title_extents.total_height + self.margin

        entries = self.entries

        base = skip
        up_to = skip
        over = False
        while up_to < len(entries) and not over:
            expected_height += table_extents[up_to].total_height
            over = expected_height >= self._max_height
            if not over:
                up_to += 1

        while base > 0 and not over:
            expected_height += table_extents[base-1].total_height
            over = expected_height >= self._max_height
            if not over:
                base -= 1

        how_many = up_to - base
        skip = base
        self.base = base

        entries = self.entries[skip:skip + how_many]

        def table_extents_iterator():
            return table_extents.iter_over(
                skip, how_many
            )

        start_x, start_y = context.get_current_point()

        start_y += title_extents.total_height
        h = title_extents.total_height
        self.title_height = h
        for (index, cell), data in zip(enumerate(title_extents), self.title):
            context.save()
            offset = title_extents.get_cell_data_left(index)
            context.rectangle(start_x + offset, start_y - h, cell.width, 2*h)
            context.clip()
            context.move_to(
                start_x + offset,
                start_y
            )
            context.show_text(data)
            context.restore()
        # start_y += self.margin

        curr_x, curr_y = start_x, start_y# + title_extents.total_height

        for line_index, (line_extent, entry) in enumerate(zip(table_extents_iterator(), entries)):
            h = line_extent.total_height
            curr_y += h
            if curr_y + self.margin >= self._max_height:
                break
            for (cell_index, cell), data in zip(enumerate(line_extent), entry):
                context.save()
                offset = line_extent.get_cell_data_left(cell_index)
                context.rectangle(curr_x + offset, curr_y - h, cell.width, 2*h)
                context.clip()
                context.move_to(
                    curr_x + offset,
                    curr_y
                )
                context.show_text(data)
                context.restore()

        curr_x = start_x
        end_x = table_extents.entries_width
        curr_y = start_y + self.margin
        end_y = table_extents.get_height_up_to(skip, how_many) + start_y + self.margin + 1

        self.table_height = end_y
        self.table_width = end_x

        for line in table_extents_iterator():
            context.move_to(curr_x, curr_y)
            context.line_to(end_x, curr_y)
            context.stroke()
            curr_y += line.total_height
        context.move_to(curr_x, curr_y)
        context.line_to(end_x, curr_y)
        context.stroke()

        curr_x = start_x
        curr_y = start_y - 1 + self.margin
        if self._how_many > 0:
            line = table_extents[0]
            for cell in line:
                context.move_to(curr_x, curr_y)
                context.line_to(curr_x, end_y)
                context.stroke()
                curr_x += cell.total_width + 2 * self.margin
            context.move_to(curr_x, curr_y)
            context.line_to(curr_x, end_y)
            context.stroke()

            if self._to_highlight is not None:
                r, g, b = global_constants.highlight
                context.set_source_rgba(r, g, b, .6)
                index = self._to_highlight
                base = start_y + table_extents.get_height_up_to(skip, index) + self.margin
                h = table_extents.get_height_of(skip, how_many, index)
                context.rectangle(start_x, base, table_extents.entries_width, h)
                context.fill()


        context.restore()

        self._shown = how_many
Example #19
0
    def draw(self, cr: cairo.Context, drawing_options: DrawingOptions):
        cr.set_source_rgb(*drawing_options.sleeper_color)
        cr.set_line_width(2)

        # Main sleepers
        cr.save()
        cr.move_to(0, 0)
        cr.line_to(25, 4 * self.coordinate_sign)
        cr.line_to(32, 4 * self.coordinate_sign)
        cr.line_to(32, -4 * self.coordinate_sign)
        cr.line_to(0, -4 * self.coordinate_sign)
        cr.close_path()
        cr.clip()

        for i in range(0, 36, 4):
            cr.move_to(i, -4)
            cr.line_to(i, 4)
        cr.stroke()
        cr.restore()

        # Branch sleepers
        cr.save()
        cr.move_to(0, 0)
        cr.line_to(25, 4 * self.coordinate_sign)
        cr.line_to(32, 4 * self.coordinate_sign)
        cr.line_to(40, 4 * self.coordinate_sign)
        cr.line_to(40, 32 * self.coordinate_sign)
        cr.line_to(0, 32 * self.coordinate_sign)
        cr.close_path()
        cr.clip()
        # cr.stroke()

        for i in range(0, 10):
            x, y, theta = self.point_position("in",
                                              i / 9 * self.branch_length,
                                              out_anchor="branch")
            theta += -math.pi / 2
            x_off, y_off = math.cos(theta) * 4, math.sin(theta) * 4
            cr.move_to(x + x_off, y + y_off)
            cr.line_to(x - x_off, y - y_off)

        cr.stroke()

        cr.restore()

        if self.state == "out":
            rail_draw_order = ("branch", "out")
        else:
            rail_draw_order = ("out", "branch")

        cr.save()

        mask = cairo.ImageSurface(
            cairo.FORMAT_ARGB32,
            math.ceil(40 * drawing_options.scale),
            math.ceil(80 * drawing_options.scale),
        )
        mask_cr = cairo.Context(mask)
        mask_cr.scale(drawing_options.scale, drawing_options.scale)
        mask_cr.translate(0, 40)
        mask_cr.set_source_rgb(0, 1, 0)

        for anchor_name in rail_draw_order:
            self.draw_rails_path(mask_cr, anchor_name)

            mask_cr.set_operator(cairo.OPERATOR_CLEAR)
            mask_cr.set_line_width(8)
            mask_cr.stroke_preserve()

            mask_cr.set_operator(cairo.OPERATOR_SOURCE)
            mask_cr.set_line_width(6)
            mask_cr.stroke_preserve()

            mask_cr.set_operator(cairo.OPERATOR_CLEAR)
            mask_cr.set_line_width(4)
            mask_cr.stroke()

        cr.set_source_rgb(*drawing_options.rail_color)

        cr.scale(1 / drawing_options.scale, 1 / drawing_options.scale)
        cr.mask_surface(mask, 0, -40 * drawing_options.scale)

        cr.restore()
Example #20
0
    def _paint_panel(self, key, width, height, panel_h):
        self.db.IUD("update web_stat_panels set HEIGHT = %s where ID = %s" % (panel_h, key))
        self.db.commit()

        color_x_line = (0.7, 0.7, 0.7)
        color_x_line_2 = (0.9, 0.9, 0.9)
        color_y_line = (0.9, 0.9, 0.9)
        color_y_line_date = (0.7, 0.7, 0.7)
        color_border = (0.5, 0.5, 0.5)

        left = 40
        right = 10
        bottom = 15

        var_ids = "0"
        series = [0, 0, 0, 0]
        typ = 0
        for row in self.db.select(
            "select SERIES_1, SERIES_2, SERIES_3, SERIES_4, TYP " "  from web_stat_panels " " where ID = " + str(key)
        ):
            series = row
            var_ids = "%s, %s, %s, %s" % row[0:4]
            typ = row[4]

        width, height = int(width), int(height)
        if width <= 0:
            width = 300
        if height <= 0:
            height = 150

        interval = self.param("range")

        delta_x = 1
        if interval == "-6 hour":
            delta_x = 6 * 3600
        elif interval == "-12 hour":
            delta_x = 12 * 3600
        elif interval == "-1 day":
            delta_x = 24 * 3600
        elif interval == "-3 day":
            delta_x = 3 * 24 * 3600
        elif interval == "-7 day":
            delta_x = 7 * 24 * 3600
        elif interval == "-14 day":
            delta_x = 14 * 24 * 3600
        elif interval == "-30 day":
            delta_x = 30 * 24 * 3600
        elif interval == "-90 day":
            delta_x = 3 * 30 * 24 * 3600
        elif interval == "-180 day":
            delta_x = 6 * 30 * 24 * 3600
        elif interval == "-360 day":
            delta_x = 12 * 30 * 24 * 3600

        min_x = int(self.param("start")) - delta_x // 2
        max_x = min_x + delta_x

        min_x_q = min_x - delta_x  # // 100
        max_x_q = max_x + delta_x  # // 100

        max_y = -9999
        min_y = 9999

        # Делаем полную выборку данных. Выкидываем подозрительные точки и
        # собираем статистику.
        prev_vals = [-9999] * 4
        chart_data = [[], [], [], []]

        zoom_step = delta_x / (width * 5)
        if zoom_step < 1 or typ != 0:
            zoom_step = 1

        x_min_max_values = []
        mi_x = max_x_q
        ma_x = min_x_q
        tt = -100

        for row in self.db.select(
            "select UNIX_TIMESTAMP(CHANGE_DATE) D, MIN(VALUE) + (MAX(VALUE) - MIN(VALUE)) VALUE, VARIABLE_ID "
            "  from core_variable_changes "
            " where VARIABLE_ID in (%s) "
            "   and CHANGE_DATE >= FROM_UNIXTIME(%s) "
            "   and CHANGE_DATE <= FROM_UNIXTIME(%s) "
            " group by 3, ROUND(UNIX_TIMESTAMP(CHANGE_DATE) / %s)"
            " order by 3, 1 " % (var_ids, min_x_q, max_x_q, zoom_step)
        ):
            ind = series.index(row[2])
            chart_data[ind] += [row]
            if row[0] > min_x and row[0] < max_x:
                max_y = max(max_y, row[1])
                min_y = min(min_y, row[1])

            if tt == -100:
                tt = row[2]

            if tt != row[2]:
                v = [tt, mi_x, ma_x]
                x_min_max_values += [v]
                mi_x = max_x_q
                ma_x = min_x_q
                tt = row[2]

            if row[0] < mi_x:
                mi_x = row[0]
            if row[0] > ma_x:
                ma_x = row[0]

        if tt != -1:
            v = [tt, mi_x, ma_x]
            x_min_max_values += [v]

        # print(x_min_max_values)
        # print(series)

        """
        for row in self.db.select("select UNIX_TIMESTAMP(CHANGE_DATE) D, VALUE, VARIABLE_ID, ID "
                                  "  from core_variable_changes "
                                  " where VARIABLE_ID in (" + var_ids + ") "
                                  "   and CHANGE_DATE >= FROM_UNIXTIME(%s) "
                                  "   and CHANGE_DATE <= FROM_UNIXTIME(%s) "
                                  "order by CHANGE_DATE " % (min_x_q, max_x_q)):
        """
        """
        try:
            self.db.IUD("set @rn := 0")

            sql = ("select UNIX_TIMESTAMP(CHANGE_DATE) D, VALUE, VARIABLE_ID, ID "
                   "  from core_variable_changes "
                   " where VARIABLE_ID in (" + var_ids + ") "
                   "   and CHANGE_DATE >= FROM_UNIXTIME(%s) "
                   "   and CHANGE_DATE <= FROM_UNIXTIME(%s) "
                   "order by CHANGE_DATE " % (min_x_q, max_x_q))

            for c in self.db.select("select count(*) c "
                                    "  from core_variable_changes "
                                    " where VARIABLE_ID in (" + var_ids + ") "
                                    "   and CHANGE_DATE >= FROM_UNIXTIME(%s) "
                                    "   and CHANGE_DATE <= FROM_UNIXTIME(%s) " % (min_x_q, max_x_q)):
                cou = c[0]
                ccc = 1000 * 4
                if cou > ccc:
                    sql = ("select UNIX_TIMESTAMP(CHANGE_DATE) D, VALUE, VARIABLE_ID, ID, @rn := @rn + 1 rownum "
                           "  from core_variable_changes "
                           " where VARIABLE_ID in (" + var_ids + ") "
                           "   and CHANGE_DATE >= FROM_UNIXTIME(%s) "
                           "   and CHANGE_DATE <= FROM_UNIXTIME(%s) "
                           "having mod(rownum, %s) = 0 "
                           "order by VARIABLE_ID, CHANGE_DATE " % (min_x_q, max_x_q, math.ceil(cou / ccc)))
            
            for row in self.db.select(sql):
                ind = series.index(row[2])
                prev_vals[ind] = row[1]

                if abs(prev_vals[ind] - row[1]) < 10:
                    chart_data[ind] += [row]
                    if row[0] > min_x and row[0] < max_x:
                        max_y = max(max_y, row[1])
                        min_y = min(min_y, row[1])
                prev_vals[ind] = row[1]
        except:
            pass
        """

        if min_y is None or max_y is None or min_y == 9999 or max_y == -9999 or min_y == max_y:
            max_y = 1
            min_y = 0

        min_y = math.floor(min_y)
        max_y = math.ceil(max_y)

        if typ == 2:
            if min_y < 0 and max_y < 0:
                max_y = 0
            elif min_y > 0 and max_y > 0:
                min_y = 0

        # Определяем цвета
        colors = [[1, 0, 0], [0, 0.65, 0.31], [0, 0, 1], [1, 0, 1]]

        off_y = (max_y - min_y) / 10
        min_y -= off_y
        max_y += off_y

        try:
            kx = (max_x - min_x) / (width - left - right)
            ky = (max_y - min_y) / (height - bottom)
            if ky == 0:
                ky = 1
        except:
            kx, ky = 1, 1

        img = ImageSurface(FORMAT_ARGB32, width, height)
        ctx = Context(img)

        width -= right
        ctx.set_line_width(1)

        # Рисуем сетку

        ctx.set_font_size(12)
        try:
            b_w, b_h = ctx.text_extents("00-00-0000")[2:4]

            # Метки на оси Y
            count = math.ceil(max_y) - math.ceil(min_y)
            space_count = math.ceil(count / ((height - bottom) / (b_h * 1.5)))
            sc = 0
            for i in range(math.ceil(min_y), math.ceil(max_y)):
                if sc == 0:
                    y = height - bottom + (min_y - i) / ky
                    ctx.set_source_rgb(*(color_x_line))
                    ctx.move_to(left, y)
                    ctx.line_to(width, y)
                    ctx.stroke()
                    ctx.set_source_rgb(0, 0, 0)
                    num = str(i)
                    tw, th = ctx.text_extents(num)[2:4]
                    ctx.move_to(left - 5 - tw, y + th // 2)
                    ctx.show_text(num)
                    sc = space_count
                sc -= 1

            # Метки на оси Х

            x_step = 3600
            if interval == "-6 hour" or interval == "-12 hour" or interval == "-1 day":
                # Дополнительно метки часов
                x_step = 3600
                for i in range(math.ceil(min_x / x_step), math.ceil(max_x / x_step)):
                    x = (i * x_step - min_x) / kx + left
                    ctx.set_source_rgb(*(color_x_line_2))
                    ctx.move_to(x, 0)
                    ctx.line_to(x, height - bottom)
                    ctx.stroke()
                    num = datetime.datetime.fromtimestamp(i * x_step).strftime("%H")
                    tw, th = ctx.text_extents(num)[2:4]
                    ctx.move_to(x + 2, height - bottom - 3)
                    ctx.set_source_rgb(*(color_x_line))
                    ctx.show_text(num)

            x_step = 3600 * 24

            space_count = 1
            count = math.ceil(max_x / x_step) - math.ceil(min_x / x_step)
            try:
                if (width / count) < b_w:
                    space_count = 2
            except:
                pass

            sc = 0
            tz = 3600 * 2
            tx_prev = -100
            for i in range(math.ceil(min_x / x_step), math.ceil(max_x / x_step) + 1):
                if sc == 0:
                    d_i = datetime.datetime.fromtimestamp(i * x_step)
                    x = (i * x_step - min_x - tz) / kx + left
                    ctx.set_source_rgb(0, 0, 0)
                    num = d_i.strftime("%d-%m-%Y %H")

                    x -= (int(d_i.strftime("%H")) * 3600 - tz) / kx

                    tw, th = ctx.text_extents(num)[2:4]
                    tx = x - tw // 2
                    ctx.move_to(tx, height - bottom + th + 5)
                    if tx - tx_prev > tw:
                        ctx.show_text(num)
                        tx_prev = tx
                        ctx.set_source_rgb(*(color_y_line_date))
                    else:
                        ctx.set_source_rgb(*(color_y_line))
                    if x >= left and x < width:
                        ctx.move_to(x, 0)
                        ctx.line_to(x, height - bottom)
                        ctx.stroke()
                    sc = space_count
                sc -= 1
        except Exception as e:
            pass

        # Рисуем верхний и правый бордер

        ctx.set_source_rgb(*color_border)
        ctx.move_to(left, 0)
        ctx.line_to(width, 0)
        ctx.line_to(width, height - bottom)
        ctx.stroke()

        # Рисуем сами графики

        ctx.rectangle(left, 0, width - left, height)
        ctx.clip()

        is_first = True
        currVarID = -1
        prevX = -1

        if typ == 0:  # Линейная
            for ind in range(4):
                """
                if len(chart_data[ind]) > 0:
                    for i in range(len(chart_data[ind]) - 1):
                        chart_data[ind][i] = list(chart_data[ind][i])
                        r1 = chart_data[ind][i]
                        r2 = chart_data[ind][i + 1]
                        chart_data[ind][i][0] += (r2[0] - r1[0]) / 2
                        chart_data[ind][i][1] += (r2[1] - r1[1]) / 2
                """
                ctx.set_source_rgb(*colors[ind])
                is_first = True
                for row in chart_data[ind]:
                    x = (row[0] - min_x) / kx + left
                    y = height - bottom - (row[1] - min_y) / ky

                    if is_first:
                        ctx.move_to(x, y)
                    else:
                        if row[0] - prevX > 10000:
                            ctx.move_to(x, y)
                        else:
                            ctx.line_to(x, y)

                    prevX = row[0]
                    is_first = False
                ctx.stroke()

        elif typ == 1:  # Точечная
            for ind in range(4):
                if chart_data[ind]:
                    ctx.set_source_rgb(*colors[ind])
                    for row in chart_data[ind]:
                        x = (row[0] - min_x) / kx + left
                        y = height - bottom - (row[1] - min_y) / ky
                        ctx.rectangle(x - 3, y - 3, 6, 6)
                    ctx.fill()
        elif typ == 2:  # Столбчатая
            cy = height - bottom - (-min_y) / ky
            for ind in range(4):
                for row in chart_data[ind]:
                    if currVarID != row[2]:
                        ctx.fill()
                        for i in range(4):
                            if series[i] == row[2]:
                                ctx.set_source_rgb(*colors[i])
                    x = (row[0] - min_x) / kx + left
                    y = height - bottom - (row[1] - min_y) / ky
                    ctx.rectangle(x - 5, y, 10, cy - y)

                    currVarID = row[2]
                ctx.fill()
        else:  # Линейчастая
            # one_vals = self._get_one_val(series, min_x_q, max_x_q)
            one_vals = self._get_one_val(series, x_min_max_values, min_x_q, max_x_q)
            for ind in range(4):
                if series[ind]:
                    is_now = True
                    for r in one_vals[ind]:
                        if r[4] == 1:
                            chart_data[ind].insert(0, r)
                        else:
                            chart_data[ind] += [r]
                            is_now = False

                    if is_now:
                        if len(chart_data[ind]) > 0:
                            r = list(chart_data[ind][len(chart_data[ind]) - 1])
                            r[0] = datetime.datetime.now().timestamp()
                            chart_data[ind] += [r]

                color = colors[ind]
                color_fill = color.copy()
                color_fill += [0.3]
                is_first = True
                y0 = height - bottom + min_y / ky
                for row in chart_data[ind]:
                    x = (row[0] - min_x) / kx + left
                    y = height - bottom - (row[1] - min_y) / ky

                    if is_first:
                        is_first = False
                    else:
                        ctx.set_source_rgb(*color)
                        ctx.move_to(prevX, prevY)
                        ctx.line_to(x, prevY)
                        ctx.line_to(x, y)
                        ctx.stroke()
                        ctx.set_source_rgba(*color_fill)
                        rx, ry, rw, rh = prevX, y0, x - prevX, prevY - y0
                        ctx.rectangle(rx, ry, rw, rh)
                        ctx.fill()

                    prevX, prevY = x, y

        # Рисуем оси

        ctx.set_source_rgb(0, 0, 0)
        ctx.move_to(left, 0)
        ctx.line_to(left, height - bottom)
        ctx.line_to(width, height - bottom)
        ctx.stroke()

        # ---------------------------

        del ctx

        byt = BytesIO()
        img.write_to_png(byt)
        byt.seek(0)
        return byt.read()