Esempio n. 1
0
 def __init__(self,
              text: str = '',
              color: Optional[str] = None,
              width: Optional[int] = None,
              truncate: Optional[int] = None,
              padding: int = 10,
              collapsible: bool = False,
              align: str = 'c',
              minwidth: int = 0) -> None:
     self.text = text if truncate is None else _truncate(text, truncate)
     self.width = max(
         (get_text_width(self.text, 11) if width is None else width) +
         padding, minwidth)
     self.color = color
     self.collapsible = collapsible
     self.align = align
Esempio n. 2
0
def graph_releases(project: str) -> Any:
    releases = [
        _Release(**row) for row in get_db().get_project_releases(project)
    ]

    row_height = 20
    bar_height_fraction = 0.75

    width = 1140
    height = row_height * len(releases)
    padding = 5
    max_fadein = 20

    max_time = time.time()
    min_time = min(min((release.start_ts for release in releases)),
                   max_time - 1.0)

    version_column_width = padding * 2 + max(
        (get_text_width(release.version, 13, True) for release in releases))
    time_column_width = width - version_column_width

    doc = XmlDocument('svg',
                      xmlns='http://www.w3.org/2000/svg',
                      width=width,
                      height=height)

    with doc.tag('linearGradient', id='untrustedFadein', x2='100%', y2=0):
        doc.tag('stop', ('offset', 0), ('stop-color', '#9f9f9f'),
                ('stop-opacity', 0))
        doc.tag('stop', ('offset', 1), ('stop-color', '#9f9f9f'),
                ('stop-opacity', 1))

    with doc.tag('linearGradient', id='trustedFadein', x2='100%', y2=0):
        doc.tag('stop', ('offset', 0), ('stop-color', '#e05d44'),
                ('stop-opacity', 0))
        doc.tag('stop', ('offset', 1), ('stop-color', '#e05d44'),
                ('stop-opacity', 1))

    doc.tag('line',
            x1=time_column_width + 0.5,
            x2=time_column_width + 0.5,
            y1=0,
            y2=height,
            stroke='#00000040')

    def draw_bar(left: float, right: Optional[float], trusted: bool) -> None:
        left_frac = (left - min_time) / (max_time - min_time)
        right_frac = (right - min_time) / (
            max_time - min_time) if right is not None else 1.0

        fillname = '#e05d44' if trusted else '#9f9f9f'

        x = left_frac * time_column_width
        width = (right_frac - left_frac) * time_column_width
        y = nrow * row_height + row_height * (1.0 -
                                              bar_height_fraction) / 2 + 0.5
        height = row_height * bar_height_fraction

        if left < config['HISTORY_CUTOFF_TIMESTAMP']:
            fadein_fillname = 'url(#trustedFadein)' if trusted else 'url(#untrustedFadein)'

            if width < max_fadein:
                # bar too small for separate fadein part, so just fill whole bar with gradient
                fillname = fadein_fillname
            else:
                # draw separated fadein part
                doc.tag('rect',
                        x=x,
                        width=max_fadein,
                        y=y,
                        height=height,
                        fill=fadein_fillname)
                x += max_fadein
                width -= max_fadein

        doc.tag('rect', x=x, width=width, y=y, height=height, fill=fillname)

    for nrow, release in enumerate(releases):
        with doc.tag('g', ('fill', '#000'),
                     ('font-family', 'DejaVu Sans,Verdana,Geneva,sans-serif'),
                     ('font-size', 13), ('font-weight', 'bold'),
                     ('text-anchor', 'left')):
            with doc.tag('text',
                         x=time_column_width + padding + 0.5,
                         y=nrow * row_height + row_height // 2 + 5 + 0.5):
                doc.text(release.version)

        if release.trusted_start_ts is not None:
            draw_bar(release.trusted_start_ts, release.end_ts, True)

            if release.trusted_start_ts != release.start_ts:
                draw_bar(release.start_ts, release.trusted_start_ts, False)
        else:
            draw_bar(release.start_ts, release.end_ts, False)

        if nrow > 0:
            doc.tag('line',
                    x1=0,
                    x2=width,
                    y1=nrow * row_height + 0.5,
                    y2=nrow * row_height + 0.5,
                    stroke='#00000040')

    return (doc.render(), {'Content-type': 'image/svg+xml'})
Esempio n. 3
0
def render_generic_badge(rows: List[List[BadgeCell]], header: Optional[str] = None, min_width: int = 0) -> Tuple[str, Dict[str, str]]:
    num_rows = len(rows)
    num_columns = len(rows[0]) if rows else 0

    column_widths = [0] * num_columns
    column_collapsed = [True] * num_columns

    # calculate column widths
    for row in rows:
        for ncol, cell in enumerate(row):
            column_widths[ncol] = max(column_widths[ncol], cell.width)
            if cell.text or not cell.collapsible:
                column_collapsed[ncol] = False

    # handle collapsed columns and finalize metrics
    column_offsets = [0] * num_columns
    for ncol in range(num_columns):
        if column_collapsed[ncol]:
            column_widths[ncol] = 0

        if ncol < num_columns - 1:
            column_offsets[ncol + 1] = column_offsets[ncol] + column_widths[ncol]

    header_height = 0
    if header:
        header_height = 25
        min_width = max(min_width, get_text_width(header, 15, bold=True) + 10)

    total_height = header_height + 20 * num_rows
    total_width = (column_offsets[-1] + column_widths[-1]) if num_columns else 0

    # force minimal width by expanding leftmost column
    if total_width < min_width:
        increment = min_width - total_width
        total_width = min_width

        for ncol in range(num_columns):
            flexible_column = 0
            if ncol == flexible_column:
                column_widths[ncol] += increment
            elif ncol > flexible_column:
                column_offsets[ncol] += increment

    doc = XmlDocument('svg', xmlns='http://www.w3.org/2000/svg', width=total_width, height=total_height)

    # define clip path for rounded corners
    with doc.tag('clipPath', id='clip'):
        doc.tag('rect', rx=3, width='100%', height='100%', fill='#000')

    # define linear gradient for bevel effect
    with doc.tag('linearGradient', id='grad', x2=0, y2='100%'):
        doc.tag('stop', ('offset', 0), ('stop-color', '#bbb'), ('stop-opacity', '.1'))
        doc.tag('stop', ('offset', 1), ('stop-opacity', '.1'))

    # graphical data
    with doc.tag('g', ('clip-path', 'url(#clip)')):
        # background
        doc.tag('rect', width='100%', height='100%', fill='#555')

        # header
        if header:
            with doc.tag('g', ('fill', '#fff'), ('text-anchor', 'middle'), ('font-family', 'DejaVu Sans,Verdana,Geneva,sans-serif'), ('font-size', 15), ('font-weight', 'bold')):
                with doc.tag('text', x=total_width / 2, y=18, fill='#010101', **{'fill-opacity': '.3'}):
                    doc.text(header)
                with doc.tag('text', x=total_width / 2, y=17):
                    doc.text(header)

        # rows
        for nrow, row in enumerate(rows):
            # cell backgrounds
            for ncol, cell in enumerate(row):
                if cell.color is None or column_collapsed[ncol]:
                    continue

                doc.tag(
                    'rect',
                    x=column_offsets[ncol],
                    y=header_height + nrow * 20,
                    width=column_widths[ncol],
                    height=20,
                    fill=cell.color
                )

            # gradient
            doc.tag('rect', y=header_height + nrow * 20, width='100%', height=20, fill='url(#grad)')

            # cell texts
            with doc.tag('g', ('fill', '#fff'), ('font-family', 'DejaVu Sans,Verdana,Geneva,sans-serif'), ('font-size', 11)):
                for ncol, cell in enumerate(row):
                    if not cell.text or column_collapsed[ncol]:
                        continue

                    text_x: float
                    if cell.align.startswith('l'):
                        text_x = column_offsets[ncol] + 5
                        text_anchor = 'start'
                    elif cell.align.startswith('r'):
                        text_x = column_offsets[ncol] + column_widths[ncol] - 5
                        text_anchor = 'end'
                    else:
                        text_x = column_offsets[ncol] + column_widths[ncol] / 2
                        text_anchor = 'middle'

                    with doc.tag('text', x=text_x, y=header_height + nrow * 20 + 15, fill='#010101', **{'fill-opacity': '.3', 'text-anchor': text_anchor}):
                        doc.text(cell.text)
                    with doc.tag('text', x=text_x, y=header_height + nrow * 20 + 14, **{'text-anchor': text_anchor}):
                        doc.text(cell.text)

    return (
        doc.render(),
        {'Content-type': 'image/svg+xml'}
    )