def floor(t, res): """Round the given date down to the nearest smaller resolution step.""" resName = res[-1] resFactor = float(res[:-1]) d = Date(t * 1000) tup = ( d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), ) if resName == "s": tup[-1] = int(tup[-1] / resFactor) * resFactor elif resName == "m": tup = tup[:5] tup[-1] = int(tup[-1] / resFactor) * resFactor elif resName == "h": tup = tup[:4] tup[-1] = int(tup[-1] / resFactor) * resFactor elif resName == "D": tup = tup[:3] tup[-1] = int( (tup[-1] - 1) / resFactor) * resFactor + 1 # days are 1-based elif resName == "W": d.setHours(0, 0, 0, 0) daysoff = (d.getDay() + 6) % 7 # Align to a monday d = Date(d.getTime() - 86_400_000 * daysoff) tup = d.getFullYear(), d.getMonth(), d.getDate() elif resName == "M": tup = tup[:2] # Note that it is zero-based (jan is zero) tup[-1] = int(tup[-1] / resFactor) * resFactor tup.extend([1]) elif resName == "Y": tup = tup[:1] tup[-1] = int(tup[-1] / resFactor) * resFactor tup.extend([0, 1]) else: raise RuntimeError("Invalid resolution: " + res) while len(tup) < 6: tup.append(0) return (Date(tup[0], tup[1], tup[2], tup[3], tup[4], tup[5]).getTime() / 1000) # cant do *tup
def add(t, delta): """Add a delta to the given date. Delta can be an int (number of seconds), or a delta string like '4h', "21s", "2M" or "2Y". Works correctly for months and keeps leap years into account. """ if isinstance(delta, (float, int)): delta = str(delta) + "s" deltaName = delta[-1] deltaFactor = float(delta[:-1]) if isNaN(deltaFactor): raise RuntimeError(f"Cannot create delta from {delta!r}") d = Date(t * 1000) tup = ( d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), ) if deltaName == "s": tup[5] += deltaFactor elif deltaName == "m": tup[4] += deltaFactor elif deltaName == "h": tup[3] += deltaFactor elif deltaName == "D": tup[2] += deltaFactor elif deltaName == "W": tup[2] += deltaFactor * 7 elif deltaName == "M": tup[1] += deltaFactor elif deltaName == "Y": tup[0] += deltaFactor else: raise RuntimeError("Invalid datetime delta: " + delta) return Date(tup[0], tup[1], tup[2], tup[3], tup[4], tup[5]).getTime() / 1000
def draw(self): PSCRIPT_OVERLOAD = False # noqa ctx = self.canvas.getContext("2d") # Prepare hidpi mode for canvas (flush state just in case) for i in range(4): ctx.restore() ctx.save() ctx.scale(self.pixel_ratio, self.pixel_ratio) # Flip y-axis ctx.scale(1, -1) ctx.translate(0, -self.height) # Clear bg ctx.clearRect(0, 0, self.width, self.height) # Determine drawing area x0 = 45 y0 = 35 width = self.width - x0 - 15 height = self.height - y0 - 5 data = data_per_db[self.dbname] if len(data) == 0: return # Get bounding box t1 = data[0].time_start t2 = data[-1].time_stop mi, ma = self._get_min_max() if ma <= mi: return hscale = width / (t2 - t1) vscale = height / (ma - mi) unix_from_utc_tuple = Date.UTC # avoid triggering new utc = get_hash_info().get("utc", False) xticks = {} # Prepare x ticks for hours (one hour is the smallest granularity) hourly_tick_units = (1, 3600), (2, 7200), (6, 21600) min_tick_dist = 60 for nhours, tick_unit in hourly_tick_units: if tick_unit * hscale >= min_tick_dist: break else: tick_unit = 0 # if tick_unit > 0: d = Date(t1 * 1000) if utc: tup = [ d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), ] tup[-1] = nhours * int(tup[-1] / nhours) t = unix_from_utc_tuple(tup[0], tup[1], tup[2], tup[3]) / 1000 else: tup = [ d.getFullYear(), d.getMonth(), d.getDate(), d.getHours() ] tup[-1] = nhours * int(tup[-1] / nhours) t = Date(tup[0], tup[1], tup[2], tup[3]).getTime() / 1000 while t <= t2: if t >= t1: d = Date(t * 1000) if utc: xticks[ t] = f"{d.getUTCHours():02i}:{d.getUTCMinutes():02i}" else: xticks[t] = f"{d.getHours():02i}:{d.getMinutes():02i}" t += tick_unit # Prepare x ticks for days/months day_tick_units = (2, 1), (2, 2), (2, 5), (1, 1), (1, 2), (1, 3), (0, 365) min_tick_dist = 60 for dindex, nsomething in day_tick_units: tick_unit = nsomething * [365 * 86400, 30 * 86400, 86400][dindex] if tick_unit * hscale >= min_tick_dist: break else: tick_unit = nsomething = 0 # n_date_ticks = 0 if nsomething > 0: d = Date(t1 * 1000) if utc: tup = [d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()] tup[dindex] = nsomething * int(tup[dindex] / nsomething) t = unix_from_utc_tuple(tup[0], tup[1], tup[2]) / 1000 else: tup = [d.getFullYear(), d.getMonth(), d.getDate()] tup[dindex] = nsomething * int(tup[dindex] / nsomething) t = Date(tup[0], tup[1], tup[2]).getTime() / 1000 while t <= t2: if t >= t1: n_date_ticks += 1 d = Date(t * 1000) if utc: dd = f"{d.getUTCDate():02i}" mm = f"{d.getUTCMonth()+1:02i}" yy = f"{d.getUTCFullYear()}" xticks[t] = f"{dd}-{mm}-{yy}" else: dd = f"{d.getDate():02i}" mm = f"{d.getMonth()+1:02i}" yy = f"{d.getFullYear()}" xticks[t] = f"{dd}-{mm}-{yy}" tup[dindex] += nsomething if utc: t = unix_from_utc_tuple(tup[0], tup[1], tup[2]) / 1000 else: t = Date(tup[0], tup[1], tup[2]).getTime() / 1000 # extra_x_tick = "" if n_date_ticks < 2: xtickskeys = xticks.keys() if len(xtickskeys) > 0 and hscale * (xtickskeys[0] - t1) < 30: xticks.pop(xtickskeys[0]) d = Date(t1 * 1000) if utc: extra_x_tick = ( f"{d.getUTCFullYear()}-{d.getUTCMonth()+1:02i}-{d.getUTCDate():02i}" ) else: extra_x_tick = ( f"{d.getFullYear()}-{d.getMonth()+1:02i}-{d.getDate():02i}" ) # Prepare y ticks yticks = self._get_ticks(vscale, mi, ma, 25) # text -> value # Prepare drawing ctx.lineWidth = 1 # Draw grid lines ctx.strokeStyle = "rgba(128, 128, 128, 0.3)" ctx.beginPath() for v, text in yticks.items(): y = y0 + (float(v) - mi) * vscale ctx.moveTo(x0, y) ctx.lineTo(x0 + width, y) ctx.stroke() # Draw x ticks ctx.strokeStyle = text_color ctx.fillStyle = text_color ctx.textAlign = "center" ctx.textBaseline = "top" # middle ctx.beginPath() for t, text in xticks.items(): x = x0 + (float(t) - t1) * hscale ctx.moveTo(x, y0) ctx.lineTo(x, y0 - 4) ctx.stroke() for t, text in xticks.items(): x = x0 + (float(t) - t1) * hscale angle = 0 # -0.15 * Math.PI x = min(x, x0 + width - 15) self._draw_text(ctx, text, x, y0 - 10, angle) if extra_x_tick: ctx.textAlign = "left" ctx.textBaseline = "bottom" self._draw_text(ctx, extra_x_tick, 0, 0) # Draw y ticks ctx.textAlign = "right" ctx.textBaseline = "middle" ctx.beginPath() for v, text in yticks.items(): y = y0 + (float(v) - mi) * vscale ctx.moveTo(x0 - 4, y) ctx.lineTo(x0, y) ctx.stroke() for v, text in yticks.items(): y = y0 + (float(v) - mi) * vscale self._draw_text(ctx, text, x0 - 8, y) # Draw axis ctx.strokeStyle = text_color ctx.beginPath() ctx.moveTo(x0, y0) ctx.lineTo(x0 + width, y0) ctx.moveTo(x0, y0) ctx.lineTo(x0, y0 + height) ctx.stroke() # Draw content self._draw_content(ctx, mi, ma, t1, t2, x0, y0, hscale, vscale) # Draw local / UTC ctx.fillStyle = "rgba(128, 128, 128, 0.5)" ctx.textAlign = "right" ctx.textBaseline = "bottom" self._draw_text(ctx, "UTC" if utc else "Local time", self.width, 0)