95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
from typing import Optional, Union
|
|
|
|
from .color import Color
|
|
from .console import Console, ConsoleOptions, RenderResult
|
|
from .jupyter import JupyterMixin
|
|
from .measure import Measurement
|
|
from .segment import Segment
|
|
from .style import Style
|
|
|
|
# There are left-aligned characters for 1/8 to 7/8, but
|
|
# the right-aligned characters exist only for 1/8 and 4/8.
|
|
BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
|
|
END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
|
|
FULL_BLOCK = "█"
|
|
|
|
|
|
class Bar(JupyterMixin):
|
|
"""Renders a solid block bar.
|
|
|
|
Args:
|
|
size (float): Value for the end of the bar.
|
|
begin (float): Begin point (between 0 and size, inclusive).
|
|
end (float): End point (between 0 and size, inclusive).
|
|
width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
|
|
color (Union[Color, str], optional): Color of the bar. Defaults to "default".
|
|
bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
size: float,
|
|
begin: float,
|
|
end: float,
|
|
*,
|
|
width: Optional[int] = None,
|
|
color: Union[Color, str] = "default",
|
|
bgcolor: Union[Color, str] = "default",
|
|
):
|
|
self.size = size
|
|
self.begin = max(begin, 0)
|
|
self.end = min(end, size)
|
|
self.width = width
|
|
self.style = Style(color=color, bgcolor=bgcolor)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"Bar({self.size}, {self.begin}, {self.end})"
|
|
|
|
def __rich_console__(
|
|
self, console: Console, options: ConsoleOptions
|
|
) -> RenderResult:
|
|
|
|
width = min(
|
|
self.width if self.width is not None else options.max_width,
|
|
options.max_width,
|
|
)
|
|
|
|
if self.begin >= self.end:
|
|
yield Segment(" " * width, self.style)
|
|
yield Segment.line()
|
|
return
|
|
|
|
prefix_complete_eights = int(width * 8 * self.begin / self.size)
|
|
prefix_bar_count = prefix_complete_eights // 8
|
|
prefix_eights_count = prefix_complete_eights % 8
|
|
|
|
body_complete_eights = int(width * 8 * self.end / self.size)
|
|
body_bar_count = body_complete_eights // 8
|
|
body_eights_count = body_complete_eights % 8
|
|
|
|
# When start and end fall into the same cell, we ideally should render
|
|
# a symbol that's "center-aligned", but there is no good symbol in Unicode.
|
|
# In this case, we fall back to right-aligned block symbol for simplicity.
|
|
|
|
prefix = " " * prefix_bar_count
|
|
if prefix_eights_count:
|
|
prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
|
|
|
|
body = FULL_BLOCK * body_bar_count
|
|
if body_eights_count:
|
|
body += END_BLOCK_ELEMENTS[body_eights_count]
|
|
|
|
suffix = " " * (width - len(body))
|
|
|
|
yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
|
|
yield Segment.line()
|
|
|
|
def __rich_measure__(
|
|
self, console: Console, options: ConsoleOptions
|
|
) -> Measurement:
|
|
return (
|
|
Measurement(self.width, self.width)
|
|
if self.width is not None
|
|
else Measurement(4, options.max_width)
|
|
)
|