コード例 #1
0
 def _draw_circle_segment(
     self,
     dr: svgwrite.Drawing,
     g: svgwrite.container.Group,
     tracks: typing.List[Track],
     a1: float,
     a2: float,
     rr: ValueRange,
     center: XY,
 ) -> None:
     length = sum([t.length() for t in tracks])
     has_special = len([t for t in tracks if t.special]) > 0
     color = self.color(self.poster.length_range_by_date, length,
                        has_special)
     max_length = self.poster.length_range_by_date.upper()
     assert max_length is not None
     r1 = rr.lower()
     assert r1 is not None
     r2 = rr.interpolate((length / max_length).magnitude)
     sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
     sin_a2, cos_a2 = math.sin(a2), math.cos(a2)
     path = dr.path(
         d=("M", center.x + r1 * sin_a1, center.y - r1 * cos_a1),
         fill=color,
         stroke="none",
     )
     path.push("l", (r2 - r1) * sin_a1, (r1 - r2) * cos_a1)
     path.push(
         f"a{r2},{r2} 0 0,0 {r2 * (sin_a2 - sin_a1)},{r2 * (cos_a1 - cos_a2)}"
     )
     path.push("l", (r1 - r2) * sin_a2, (r2 - r1) * cos_a2)
     date_title = str(tracks[0].start_time().date())
     str_length = utils.format_float(self.poster.m2u(length))
     path.set_desc(title=f"{date_title} {str_length} {self.poster.u()}")
     g.add(path)
コード例 #2
0
 def _draw_rings(self, dr: svgwrite.Drawing, g: svgwrite.container.Group,
                 center: XY, radius_range: ValueRange) -> None:
     length_range = self.poster.length_range_by_date
     if not length_range.is_valid():
         return
     min_length = length_range.lower()
     max_length = length_range.upper()
     assert min_length is not None
     assert max_length is not None
     ring_distance = self._determine_ring_distance(max_length)
     if ring_distance is None:
         return
     distance = ring_distance
     while distance < max_length:
         radius = radius_range.interpolate(
             (distance / max_length).magnitude)
         g.add(
             dr.circle(
                 center=center.tuple(),
                 r=radius,
                 stroke=self._ring_color,
                 stroke_opacity="0.2",
                 fill="none",
                 stroke_width=0.3,
             ))
         distance += ring_distance
コード例 #3
0
 def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY,
          offset: XY) -> None:
     """Draw the heatmap based on tracks."""
     bbox = self._determine_bbox()
     year_groups: typing.Dict[int, svgwrite.container.Group] = {}
     for tr in self.poster.tracks:
         year = tr.start_time().year
         if year not in year_groups:
             g_year = dr.g(id=f"year{year}")
             g.add(g_year)
             year_groups[year] = g_year
         else:
             g_year = year_groups[year]
         color = self.color(self.poster.length_range, tr.length(),
                            tr.special)
         for line in utils.project(bbox, size, offset, tr.polylines):
             for opacity, width in [(0.1, 5.0), (0.2, 2.0), (1.0, 0.3)]:
                 g_year.add(
                     dr.polyline(
                         points=line,
                         stroke=color,
                         stroke_opacity=opacity,
                         fill="none",
                         stroke_width=width,
                         stroke_linejoin="round",
                         stroke_linecap="round",
                     ))
コード例 #4
0
 def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY,
          offset: XY) -> None:
     """For each track, draw it on the poster."""
     if self.poster.tracks is None:
         raise PosterError("No tracks to draw.")
     cell_size, counts = utils.compute_grid(len(self.poster.tracks), size)
     if cell_size is None or counts is None:
         raise PosterError("Unable to compute grid.")
     count_x, count_y = counts[0], counts[1]
     spacing_x = 0 if count_x <= 1 else (size.x - cell_size * count_x) / (
         count_x - 1)
     spacing_y = 0 if count_y <= 1 else (size.y - cell_size * count_y) / (
         count_y - 1)
     offset.x += (size.x - count_x * cell_size -
                  (count_x - 1) * spacing_x) / 2
     offset.y += (size.y - count_y * cell_size -
                  (count_y - 1) * spacing_y) / 2
     year_groups: typing.Dict[int, svgwrite.container.Group] = {}
     for (index, tr) in enumerate(self.poster.tracks):
         year = tr.start_time().year
         if year not in year_groups:
             g_year = dr.g(id=f"year{year}")
             g.add(g_year)
             year_groups[year] = g_year
         else:
             g_year = year_groups[year]
         p = XY(index % count_x, index // count_x) * XY(
             cell_size + spacing_x, cell_size + spacing_y)
         self._draw_track(
             dr,
             g_year,
             tr,
             0.9 * XY(cell_size, cell_size),
             offset + 0.05 * XY(cell_size, cell_size) + p,
         )
コード例 #5
0
    def _draw_track(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, tr: Track, size: XY, offset: XY) -> None:
        color = self.color(self.poster.length_range, tr.length(), tr.special)
        str_length = utils.format_float(self.poster.m2u(tr.length()))

        date_title = str(tr.start_time.date()) if tr.start_time else "Unknown Date"
        for line in utils.project(tr.bbox(), size, offset, tr.polylines):
            polyline = dr.polyline(
                points=line,
                stroke=color,
                fill="none",
                stroke_width=0.5,
                stroke_linejoin="round",
                stroke_linecap="round",
            )
            polyline.set_desc(title=f"{date_title} {str_length} {self.poster.u()}")
            g.add(polyline)
コード例 #6
0
    def _draw_year(self, dr: svgwrite.Drawing, g: svgwrite.container.Group,
                   size: XY, offset: XY, year: int) -> None:
        min_size = min(size.x, size.y)
        outer_radius = 0.5 * min_size - 6
        radius_range = ValueRange.from_pair(outer_radius / 4, outer_radius)
        center = offset + 0.5 * size

        if self._rings:
            self._draw_rings(dr, g, center, radius_range)

        year_style = f"dominant-baseline: central; font-size:{min_size * 4.0 / 80.0}px; font-family:Arial;"
        month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;"

        g.add(
            dr.text(
                f"{year}",
                insert=center.tuple(),
                fill=self.poster.colors["text"],
                text_anchor="middle",
                alignment_baseline="middle",
                style=year_style,
            ))
        df = 360.0 / (366 if calendar.isleap(year) else 365)
        day = 0
        date = datetime.date(year, 1, 1)
        while date.year == year:
            text_date = date.strftime("%Y-%m-%d")
            a1 = math.radians(day * df)
            a2 = math.radians((day + 1) * df)
            if date.day == 1:
                (_, last_day) = calendar.monthrange(date.year, date.month)
                a3 = math.radians((day + last_day - 1) * df)
                sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
                sin_a3, cos_a3 = math.sin(a3), math.cos(a3)
                r1 = outer_radius + 1
                r2 = outer_radius + 6
                r3 = outer_radius + 2
                g.add(
                    dr.line(
                        start=(center + r1 * XY(sin_a1, -cos_a1)).tuple(),
                        end=(center + r2 * XY(sin_a1, -cos_a1)).tuple(),
                        stroke=self.poster.colors["text"],
                        stroke_width=0.3,
                    ))
                path = dr.path(
                    d=("M", center.x + r3 * sin_a1, center.y - r3 * cos_a1),
                    fill="none",
                    stroke="none",
                )
                path.push(
                    f"a{r3},{r3} 0 0,1 {r3 * (sin_a3 - sin_a1)},{r3 * (cos_a1 - cos_a3)}"
                )
                g.add(path)
                tpath = svgwrite.text.TextPath(
                    path,
                    self.poster.month_name(date.month),
                    startOffset=(0.5 * r3 * (a3 - a1)))
                text = dr.text(
                    "",
                    fill=self.poster.colors["text"],
                    text_anchor="middle",
                    style=month_style,
                )
                text.add(tpath)
                g.add(text)
            if text_date in self.poster.tracks_by_date:
                self._draw_circle_segment(
                    dr,
                    g,
                    self.poster.tracks_by_date[text_date],
                    a1,
                    a2,
                    radius_range,
                    center,
                )

            day += 1
            date += datetime.timedelta(1)
コード例 #7
0
    def _draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group,
              size: XY, offset: XY, year: int) -> None:
        min_size = min(size.x, size.y)
        year_size = min_size * 4.0 / 80.0
        year_style = f"font-size:{year_size}px; font-family:Arial;"
        month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;"
        day_style = f"dominant-baseline: central; font-size:{min_size * 1.0 / 80.0}px; font-family:Arial;"
        day_length_style = f"font-size:{min_size * 1.0 / 80.0}px; font-family:Arial;"

        g.add(
            dr.text(
                f"{year}",
                insert=offset.tuple(),
                fill=self.poster.colors["text"],
                alignment_baseline="hanging",
                style=year_style,
            ))
        offset.y += year_size
        size.y -= year_size
        count_x = 31
        for month in range(1, 13):
            date = datetime.date(year, month, 1)
            (_, last_day) = calendar.monthrange(year, month)
            count_x = max(count_x, date.weekday() + last_day)

        cell_size = min(size.x / count_x, size.y / 36)
        spacing = XY(
            (size.x - cell_size * count_x) / (count_x - 1),
            (size.y - cell_size * 3 * 12) / 11,
        )

        for month in range(1, 13):
            date = datetime.date(year, month, 1)
            y = month - 1
            y_pos = offset.y + (y * 3 + 1) * cell_size + y * spacing.y
            g.add(
                dr.text(
                    self.poster.month_name(month),
                    insert=(offset.x, y_pos - 2),
                    fill=self.poster.colors["text"],
                    alignment_baseline="hanging",
                    style=month_style,
                ))

            day_offset = date.weekday()
            while date.month == month:
                x = date.day - 1
                x_pos = offset.x + (day_offset + x) * cell_size + x * spacing.x
                pos = (x_pos + 0.05 * cell_size, y_pos + 1.15 * cell_size)
                dim = (cell_size * 0.9, cell_size * 0.9)
                text_date = date.strftime("%Y-%m-%d")
                if text_date in self.poster.tracks_by_date:
                    tracks = self.poster.tracks_by_date[text_date]
                    length = sum([t.length() for t in tracks])
                    has_special = len([t for t in tracks if t.special]) > 0
                    color = self.color(self.poster.length_range_by_date,
                                       length, has_special)
                    g.add(dr.rect(pos, dim, fill=color))
                    g.add(
                        dr.text(
                            utils.format_float(self.poster.m2u(length)),
                            insert=(
                                pos[0] + cell_size / 2,
                                pos[1] + cell_size + cell_size / 2,
                            ),
                            text_anchor="middle",
                            style=day_length_style,
                            fill=self.poster.colors["text"],
                        ))
                else:
                    g.add(dr.rect(pos, dim, fill="#444444"))

                g.add(
                    dr.text(
                        localized_day_of_week_name(date.weekday(), short=True),
                        insert=(
                            offset.x + (day_offset + x) * cell_size +
                            cell_size / 2,
                            pos[1] + cell_size / 2,
                        ),
                        text_anchor="middle",
                        alignment_baseline="middle",
                        style=day_style,
                    ))
                date += datetime.timedelta(1)
コード例 #8
0
    def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY,
             offset: XY) -> None:
        if self.poster.tracks is None:
            raise PosterError("No tracks to draw")
        year_size = 200 * 4.0 / 80.0
        year_style = f"font-size:{year_size}px; font-family:Arial;"
        year_length_style = f"font-size:{110 * 3.0 / 80.0}px; font-family:Arial;"
        month_names_style = "font-size:2.5px; font-family:Arial"
        total_length_year_dict = self.poster.total_length_year_dict
        for year in self.poster.years.iter():
            g_year = dr.g(id=f"year{year}")
            g.add(g_year)

            start_date_weekday, _ = calendar.monthrange(year, 1)
            github_rect_first_day = datetime.date(year, 1, 1)
            # Github profile the first day start from the last Monday of the last year or the first Monday of this year
            # It depands on if the first day of this year is Monday or not.
            github_rect_day = github_rect_first_day + datetime.timedelta(
                -start_date_weekday)
            year_length = total_length_year_dict.get(year, 0)
            year_length_str = utils.format_float(self.poster.m2u(year_length))
            month_names = [
                locale.nl_langinfo(day)[:3]  # Get only first three letters
                for day in [
                    locale.MON_1,
                    locale.MON_2,
                    locale.MON_3,
                    locale.MON_4,
                    locale.MON_5,
                    locale.MON_6,
                    locale.MON_7,
                    locale.MON_8,
                    locale.MON_9,
                    locale.MON_10,
                    locale.MON_11,
                    locale.MON_12,
                ]
            ]
            km_or_mi = self.poster.u()
            g_year.add(
                dr.text(
                    f"{year}",
                    insert=offset.tuple(),
                    fill=self.poster.colors["text"],
                    alignment_baseline="hanging",
                    style=year_style,
                ))

            g_year.add(
                dr.text(
                    f"{year_length_str} {km_or_mi}",
                    insert=(offset.tuple()[0] + 165, offset.tuple()[1] + 2),
                    fill=self.poster.colors["text"],
                    alignment_baseline="hanging",
                    style=year_length_style,
                ))
            # add month name up to the poster one by one because of svg text auto trim the spaces.
            for num, name in enumerate(month_names):
                g_year.add(
                    dr.text(
                        f"{name}",
                        insert=(offset.tuple()[0] + 15.5 * num,
                                offset.tuple()[1] + 14),
                        fill=self.poster.colors["text"],
                        style=month_names_style,
                    ))

            rect_x = 10.0
            dom = (2.6, 2.6)
            # add every day of this year for 53 weeks and per week has 7 days
            for _i in range(54):
                rect_y = offset.y + year_size + 2
                for _j in range(7):
                    if int(github_rect_day.year) > year:
                        break
                    rect_y += 3.5
                    color = "#444444"
                    date_title = str(github_rect_day)
                    if date_title in self.poster.tracks_by_date:
                        tracks = self.poster.tracks_by_date[date_title]
                        length = sum([t.length() for t in tracks])
                        distance1 = self.poster.special_distance[
                            "special_distance"]
                        distance2 = self.poster.special_distance[
                            "special_distance2"]
                        has_special = distance1 < length < distance2
                        color = self.color(self.poster.length_range_by_date,
                                           length, has_special)
                        if length >= distance2:
                            special_color = self.poster.colors.get(
                                "special2") or self.poster.colors.get(
                                    "special")
                            if special_color is not None:
                                color = special_color
                        str_length = utils.format_float(
                            self.poster.m2u(length))
                        date_title = f"{date_title} {str_length} {km_or_mi}"

                    rect = dr.rect((rect_x, rect_y), dom, fill=color)
                    rect.set_desc(title=date_title)
                    g_year.add(rect)
                    github_rect_day += datetime.timedelta(1)
                rect_x += 3.5
            offset.y += 3.5 * 9 + year_size + 1.5