def main(): from mcserverstats import logalyzer logs = logalyzer.LogDirectory('test_logs/') timeline_data = get_timeline_data(logs, None, None) t_start, t_end = timeline_data[:2] title = '%s to %s' % (timeutils.human_date_str(timeutils.epoch_to_date_str(t_start)), timeutils.human_date_str(timeutils.epoch_to_date_str(t_end)) or 'last activity') draw_timeline(timeline_data, 'test.png', title) write_timeline_html_page(timeline_data, 'test.html', title)
def test_epoch_to_date_str(self): self.assertEqual('2015-03-12 00:00:00', timeutils.epoch_to_date_str(1426114800)) self.assertEqual('2015-03-12 00:00:09', timeutils.epoch_to_date_str(1426114809)) self.assertEqual('2015-03-12 01:10:01', timeutils.epoch_to_date_str(1426119001))
def get_timeline_html(timeline_data, title='', hour_width=30, html_style_players=default_html_style_players): t_start, t_end, lines, uptimes = timeline_data hour_width = int(hour_width) sec_per_hour = 3600 sec_to_screen = lambda t: t * hour_width / sec_per_hour template_session = '<span style="left:%(start).3fpx;width:%(duration).3fpx"></span>' def session_to_html(t_from, t_to): start = sec_to_screen(t_from - t_start) duration = sec_to_screen(t_to - t_from) return template_session % { 'start': start, 'duration': duration - 4, } template_player_sessions = \ '<tr><td class="tl_sessions tl_user_%(name)s">' \ '%(sessions)s' \ '</td><td><img height="16px" src="%(head_b64)s"/> %(name)s</td></tr>' html_all_player_sessions = [] for line in lines: last_name = line[-1][-1] html_sessions = '' for uuid, t_from, t_to, name in line: html_sessions += session_to_html(t_from, t_to) html_all_player_sessions.append(template_player_sessions % { 'name': last_name, 'sessions': html_sessions, 'head_b64': player_head_img_base64(last_name), }) html_all_player_sessions = ''.join(html_all_player_sessions) html_uptimes = ''.join(session_to_html(t_from, t_to) for t_from, t_to in uptimes) html_hours = [] bg_offset = None for sec in range((t_start // sec_per_hour) * sec_per_hour, t_end + sec_per_hour, sec_per_hour): if sec <= t_start or sec >= t_end: continue # out of range by rounding if bg_offset is None: # first drawn hour bg_offset = sec_to_screen(sec - t_start) hour_text = timeutils.epoch_to_date_str(sec, '%k') # hour 0-23, space padded html_hours.append('<span>%s</span>' % hour_text) html_hours = ''.join(html_hours) page_data = { 'title_text': title, 'hour_width': hour_width, 'bg_scale_b64': bg_scale_base64(hour_width), 'bg_offset': bg_offset, 'hour_offset': bg_offset - hour_width/2, 'sessions_width': sec_to_screen(t_end - t_start), 'style_players': html_style_players, 'hours': html_hours, 'uptimes': html_uptimes, 'players': html_all_player_sessions, } template_timeline = \ '<style type="text/css">' \ '.timeline td{margin:0px;padding:0px}' \ '.timeline span{display:inline-block;overflow:hidden}' \ '.tl_hours span{width:%(hour_width)ipx;text-align:center}' \ '.tl_uptimes,.tl_sessions{background-image:url(%(bg_scale_b64)s);background-position:%(bg_offset).3fpx 0px;background-repeat:repeat;position:relative;height:20px}' \ '.tl_uptimes span,.tl_sessions span{background-color:rgba(128,128,128,0.3);border:2px solid rgba(0,0,0,0.4);height:12px;position:absolute;top:2px;' \ 'border-radius:8px;-moz-border-radius:8px}' \ '.tl_uptimes span{background-color:#0f0;border-color:black;height:6px;top:5px}' \ '%(style_players)s' \ '</style><table class="timeline" style="border-collapse:collapse">' \ '<tr><td class="tl_hours" style="min-width:%(sessions_width).3fpx;padding-left:%(hour_offset).3fpx">' \ '%(hours)s' \ '</td><td>Hours</td></tr>' \ '<tr><td class="tl_uptimes">' \ '%(uptimes)s' \ '</td><td>Server online</td></tr>' \ '%(players)s' \ '<tr><td colspan=2 align="center" class="tl_title" style="font-size:20px">%(title_text)s</td></tr>' \ '</table>' return template_timeline % page_data
def draw_timeline(timeline_data, img_path, title='', im_width=None, settings=default_settings, **kwargs): """ /-----border------\ | |||||scale||||| | | ||line_border|| | | ----uptimes---- | | ||line_border|| | | /-name_border-\ | | | NAME ====== | | | \-name_border-/ | | line_border | | (===) (====) | | line_border | | (==========) | | ||line_border|| | |---title_border--| | [TITLE] | \-----border------/ """ for key in kwargs: settings[key] = kwargs[key] s = SettingsDict(**settings) t_start, t_end, lines, uptimes = timeline_data if not im_width: im_width = int((t_end - t_start) / 3600 * s.scale_gap) # TODO # calculate sizes title_box_height = s.title_height + s.title_border \ if len(title) > 0 and s.title_height > 0 else 0 scale_box_height = s.scale_height + 2 * s.line_border \ if s.scale_height > 0 else 0 line_height = 2 * s.name_border + s.name_height line_box_height = s.line_border + line_height im_height = 2 * s.border \ + title_box_height + scale_box_height + s.uptimes_height + s.line_border \ + line_box_height * (len(lines) - 1) + line_height inner_width = im_width - 2 * s.border h_stretch = inner_width / (t_end - t_start) s.name_radius = min(s.name_radius, s.name_height / 2 + s.name_border) name_horiz_border = max(s.name_border, s.name_radius) # start drawing surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, im_width, im_height) c = cairo.Context(surface) c.select_font_face(s.font_name) c.set_source_rgba(*s.bg_color) c.paint() # draw title c.set_font_size(s.title_height) draw_text(c, title, s.title_color, s.border, im_height - (s.border + s.title_height / 2), CENTER, im_width - s.border, shadow=(s.title_shadow_color, s.title_shadow_offset)) # draw scale c.set_source_rgba(*s.scale_color) c.set_line_width(s.scale_line_width) c.set_font_size(s.scale_height) scale_line_y = s.border + s.scale_height scale_line_dy = line_box_height * len(lines) + s.line_border * 2 + s.uptimes_height for sec in range((t_start // 3600) * 3600, t_end + 3600, 3600): if sec < t_start or sec > t_end: continue # out of range by rounding hour_text = timeutils.epoch_to_date_str(sec, '%k') # hour 0-23, space padded if hour_text == ' 0': c.set_line_width(s.scale_line_width_midnight) elif hour_text == '12': c.set_line_width(s.scale_line_width_noon) scale_line_x = int(s.border + (sec - t_start) * h_stretch) - c.get_line_width() / 2 c.move_to(scale_line_x, scale_line_y) c.rel_line_to(0, scale_line_dy) c.stroke() if hour_text in (' 0', '12'): c.set_line_width(s.scale_line_width) # TODO rotate by 90 degrees draw_text(c, hour_text, s.scale_color, scale_line_x, s.border + s.scale_height / 2, CENTER, None, shadow=(s.scale_shadow_color, s.scale_shadow_offset)) # draw uptimes for t_from, t_to in uptimes: x = s.border + (t_from - t_start) * h_stretch w = (t_to - t_from) * h_stretch draw_rounded_rect(c, s.uptimes_color, x, scale_box_height, w, s.uptimes_height, s.uptimes_height, (2, 0,0,0)) # draw sessions y_offset = s.border + scale_box_height + s.uptimes_height c.set_font_size(s.name_height) for i, line in enumerate(lines): y = i * line_box_height + y_offset for session in line: uuid, t_from, t_to, name = session color = s.color_from_uuid(uuid, settings) x = s.border + (t_from - t_start) * h_stretch w = (t_to - t_from) * h_stretch draw_rounded_rect(c, color, x, y, w, line_height, s.name_radius, dark_border(2, color)) t_x = x + name_horiz_border h_y = y + s.name_border t_y = h_y + s.name_height / 2 if w > s.name_height + 2 * s.name_border: draw_head(c, t_x, h_y, s.name_height, name) draw_text(c, name, s.name_color, t_x, t_y, max_w=w - 2 * name_horiz_border, shadow=(s.name_shadow_color, s.name_shadow_offset)) # save image surface.write_to_png(img_path)