from typing import Sequence from .charts_axis_types import Axis, AxisOrient, AxisTick, Line from .math_round_float import round_float # d3-axis's default gap between the end of a tick mark and its label. _LABEL_PADDING = 3 def axis_pixel(value: float) -> float: """A pixel coordinate rounded to 2 places, the precision every axis output uses.""" return round_float(value, 2) def axis_line(x1: float, y1: float, x2: float, y2: float) -> Line: return Line(x1=axis_pixel(x1), y1=axis_pixel(y1), x2=axis_pixel(x2), y2=axis_pixel(y2)) def check_orient(orient: str) -> None: if orient not in ("bottom", "top", "left", "right"): raise ValueError('orient must be bottom, top, left or right, received "%s"' % (orient,)) def axis_from_ticks( positions: Sequence[float], labels: Sequence[str], range: Sequence[float], # noqa: A002 - the manifest names it range orient: AxisOrient, position: float, tick_size: float, ) -> Axis: """Axis geometry from tick positions and labels, for any scale (d3-axis layout).""" check_orient(orient) if len(positions) != len(labels): raise ValueError( "positions and labels must be the same length, received %d and %d" % (len(positions), len(labels)) ) if len(range) != 2: raise ValueError("range must have exactly 2 values, [from, to]; got %d" % len(range)) horizontal = orient in ("bottom", "top") sign = 1 if orient in ("bottom", "right") else -1 mark_end = position + sign * tick_size label_at = position + sign * (tick_size + _LABEL_PADDING) ticks = [] for p, label in zip(positions, labels): if horizontal: ticks.append( AxisTick( position=axis_pixel(p), label=label, tick=axis_line(p, position, p, mark_end), label_x=axis_pixel(p), label_y=axis_pixel(label_at), anchor="middle", baseline="hanging" if orient == "bottom" else "alphabetic", ) ) else: ticks.append( AxisTick( position=axis_pixel(p), label=label, tick=axis_line(position, p, mark_end, p), label_x=axis_pixel(label_at), label_y=axis_pixel(p), anchor="end" if orient == "left" else "start", baseline="middle", ) ) if horizontal: line = axis_line(range[0], position, range[1], position) else: line = axis_line(position, range[0], position, range[1]) return Axis(orient=orient, position=axis_pixel(position), line=line, ticks=ticks)