def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: """Draw the circular Poster using distances broken down by time""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") if self.poster.length_range_by_date is None: return years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 4) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in self.poster.years.iter(): g_year = dr.g(id=f"year{year}") g.add(g_year) self._draw_year(dr, g_year, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
def project( bbox: s2sphere.LatLngRect, size: XY, offset: XY, latlnglines: typing.List[typing.List[s2sphere.LatLng]] ) -> typing.List[typing.List[typing.Tuple[float, float]]]: min_x = lng2x(bbox.lng_lo().degrees) d_x = lng2x(bbox.lng_hi().degrees) - min_x while d_x >= 2: d_x -= 2 while d_x < 0: d_x += 2 min_y = lat2y(bbox.lat_lo().degrees) max_y = lat2y(bbox.lat_hi().degrees) d_y = abs(max_y - min_y) scale = size.x / d_x if size.x / size.y <= d_x / d_y else size.y / d_y offset = offset + 0.5 * (size - scale * XY(d_x, -d_y)) - scale * XY(min_x, min_y) lines = [] for latlngline in latlnglines: line = [] for latlng in latlngline: if bbox.contains(latlng): line.append((offset + scale * latlng2xy(latlng)).tuple()) else: if len(line) > 0: lines.append(line) line = [] if len(line) > 0: lines.append(line) return lines
def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: """Iterate through the Poster's years, creating a calendar for each.""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 8) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in self.poster.years.iter(): g_year = dr.g(id=f"year{year}") g.add(g_year) self._draw(dr, g_year, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
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, )
def draw(self, dr: svgwrite.Drawing, 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 for (index, tr) in enumerate(self.poster.tracks): p = XY(index % count_x, index // count_x) * XY( cell_size + spacing_x, cell_size + spacing_y) self._draw_track( dr, tr, 0.9 * XY(cell_size, cell_size), offset + 0.05 * XY(cell_size, cell_size) + p, )
def draw(self, dr: svgwrite.Drawing, size: XY, offset: XY): """Iterate through the Poster's years, creating a calendar for each.""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 8) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in range(self.poster.years.from_year, self.poster.years.to_year + 1): self._draw(dr, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
def draw(self, drawer: "TracksDrawer", output: str) -> None: """Set the Poster's drawer and draw the tracks.""" self.tracks_drawer = drawer d = svgwrite.Drawing(output, (f"{self.width}mm", f"{self.height}mm")) d.viewbox(0, 0, self.width, self.height) d.add(d.rect((0, 0), (self.width, self.height), fill=self.colors["background"])) self._draw_header(d) self._draw_footer(d) self._draw_tracks(d, XY(self.width - 20, self.height - 30 - 30), XY(10, 30)) d.save()
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)
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)
def latlng2xy(latlng: s2sphere.LatLng) -> XY: return XY(lng2x(latlng.lng().degrees), lat2y(latlng.lat().degrees))
def _draw(self, dr: svgwrite.Drawing, size: XY, offset: XY, year: int): 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;" dr.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, ) # chinese weekday key number is the third. keyword_num = 0 if locale.getlocale()[0] == "zh_CN": keyword_num = 2 # first character of localized day names, starting with Monday. dow = [ locale.nl_langinfo(day)[keyword_num].upper() for day in [ locale.DAY_2, locale.DAY_3, locale.DAY_4, locale.DAY_5, locale.DAY_6, locale.DAY_7, locale.DAY_1, ] ] 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 dr.add( dr.text( date.strftime("%B"), 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 ) dr.add(dr.rect(pos, dim, fill=color)) dr.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: dr.add(dr.rect(pos, dim, fill="#444444")) dr.add( dr.text( dow[date.weekday()], 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)