bar.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. from typing import Optional, Union
  2. from .color import Color
  3. from .console import Console, ConsoleOptions, RenderResult
  4. from .jupyter import JupyterMixin
  5. from .measure import Measurement
  6. from .segment import Segment
  7. from .style import Style
  8. # There are left-aligned characters for 1/8 to 7/8, but
  9. # the right-aligned characters exist only for 1/8 and 4/8.
  10. BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
  11. END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
  12. FULL_BLOCK = "█"
  13. class Bar(JupyterMixin):
  14. """Renders a solid block bar.
  15. Args:
  16. size (float): Value for the end of the bar.
  17. begin (float): Begin point (between 0 and size, inclusive).
  18. end (float): End point (between 0 and size, inclusive).
  19. width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
  20. color (Union[Color, str], optional): Color of the bar. Defaults to "default".
  21. bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
  22. """
  23. def __init__(
  24. self,
  25. size: float,
  26. begin: float,
  27. end: float,
  28. *,
  29. width: Optional[int] = None,
  30. color: Union[Color, str] = "default",
  31. bgcolor: Union[Color, str] = "default",
  32. ):
  33. self.size = size
  34. self.begin = max(begin, 0)
  35. self.end = min(end, size)
  36. self.width = width
  37. self.style = Style(color=color, bgcolor=bgcolor)
  38. def __repr__(self) -> str:
  39. return f"Bar({self.size}, {self.begin}, {self.end})"
  40. def __rich_console__(
  41. self, console: Console, options: ConsoleOptions
  42. ) -> RenderResult:
  43. width = min(
  44. self.width if self.width is not None else options.max_width,
  45. options.max_width,
  46. )
  47. if self.begin >= self.end:
  48. yield Segment(" " * width, self.style)
  49. yield Segment.line()
  50. return
  51. prefix_complete_eights = int(width * 8 * self.begin / self.size)
  52. prefix_bar_count = prefix_complete_eights // 8
  53. prefix_eights_count = prefix_complete_eights % 8
  54. body_complete_eights = int(width * 8 * self.end / self.size)
  55. body_bar_count = body_complete_eights // 8
  56. body_eights_count = body_complete_eights % 8
  57. # When start and end fall into the same cell, we ideally should render
  58. # a symbol that's "center-aligned", but there is no good symbol in Unicode.
  59. # In this case, we fall back to right-aligned block symbol for simplicity.
  60. prefix = " " * prefix_bar_count
  61. if prefix_eights_count:
  62. prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
  63. body = FULL_BLOCK * body_bar_count
  64. if body_eights_count:
  65. body += END_BLOCK_ELEMENTS[body_eights_count]
  66. suffix = " " * (width - len(body))
  67. yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
  68. yield Segment.line()
  69. def __rich_measure__(
  70. self, console: Console, options: ConsoleOptions
  71. ) -> Measurement:
  72. return (
  73. Measurement(self.width, self.width)
  74. if self.width is not None
  75. else Measurement(4, options.max_width)
  76. )