Source code for lightningchart.charts

from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Callable, Any
import uuid

try:
    from typing import Unpack
except ImportError:
    from typing_extensions import Unpack

from lightningchart.instance import Instance
from lightningchart.ui.axis import DefaultAxis, DefaultAxis3D, Axis
from lightningchart.ui.cursor import Cursor2D, Cursor3D, CursorXY
from lightningchart.ui.legend import Legend
from lightningchart.ui.text_box import TextBox, PointableTextBox
from lightningchart.ui.check_box import CheckBox
from lightningchart.ui.button_box import ButtonBox
from lightningchart.utils import convert_to_base64, convert_color_to_hex
from lightningchart.utils.utils import ColorInput, LegendOptions, PaddingKwargs

if TYPE_CHECKING:
    from lightningchart.ui.cursor import CursorXY, Cursor2D, Cursor3D


[docs] class Chart: def __init__(self, instance: Instance): self.id = str(uuid.uuid4()).split('-')[0] self.instance = instance self._legend = None @property def legend(self): """Access to chart's default legend.""" if self._legend is None: self._legend = Legend(self) return self._legend
[docs] def open( self, method: str = None, live=False, width: int | str = '100%', height: int | str = 600, ): """Open the rendering view. Method "browser" will open the chart in your browser. Method "notebook" will display the chart in a notebook environment with an IFrame component. Method "link" will return a URL of the chart that can be used to embed it in external applications. Args: method (str): "browser" | "notebook" live (bool): Whether to use real-time rendering or not. width (int): The width of the IFrame component in pixels. height (int): The height of the IFrame component in pixels. Returns: self | str: Returns a URL string if method is "link", otherwise returns the class instance. """ return self.instance.open(method=method, live=live, width=width, height=height) or self
[docs] def close(self): """Close the connection to a chart with real-time display mode. Note: This will terminate the current Python instance! """ self.instance.close() return self
[docs] def set_data_preservation(self, enabled: bool): """Enable or disable server-side data preservation for real-time visualization use cases. Enabled by default! Args: enabled (bool): Boolean flag. Returns: The instance of the class for fluent interface. """ self.instance.set_data_preservation(enabled) return self
[docs] class GeneralMethods(Chart):
[docs] def save_to_file( self, file_name: str = None, image_format: str = 'image/png', image_quality: float = 0.92, scale: float = None, ): """Save the current rendering view as a screenshot. Args: file_name (str): Name of prompted download file as string. File extension shouldn't be included as it is automatically detected from 'type'-argument. image_format (str): A DOMString indicating the image format. The default format type is image/png. image_quality (float): A Number between 0 and 1 indicating the image quality to use for image formats that use lossy compression such as image/jpeg and image/webp. If this argument is anything else, the default value for image quality is used. The default value is 0.92. scale (float): Convenience output scaling factor. This doesn't actually stretch the result, but instead draws an altered scaled version and captures that. Returns: The instance of the class for fluent interface. """ if file_name is None: file_name = f'LightningChart_Python_{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}' self.instance.send( self.id, 'saveToFile', { 'fileName': file_name, 'type': image_format, 'encoderOptions': image_quality, 'scale': scale, }, ) return self
[docs] def dispose(self): """Permanently destroy the component.""" self.instance.send(self.id, 'dispose')
[docs] def set_animations_enabled(self, enabled: bool = True): """Disable/Enable all animations of the Chart. Args: enabled (bool): Boolean value to enable or disable animations. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setAnimationsEnabled', {'enabled': enabled}) return self
[docs] def set_padding(self, *args, **kwargs: Unpack[PaddingKwargs]): """Set padding around the chart in pixels. Args: *args: A single numeric value (int or float) for uniform padding on all sides. **kwargs: Optional named arguments to specify padding for individual sides: - `left` (int or float): Padding for the left side. - `right` (int or float): Padding for the right side. - `top` (int or float): Padding for the top side. - `bottom` (int or float): Padding for the bottom side. Examples: - `set_padding(5)`: Sets uniform padding for all sides (integer or float). - `set_padding(left=10, top=15)`: Sets padding for specific sides only. - `set_padding(left=10, top=15, right=20, bottom=25)`: Fully define padding for all sides. Returns: The instance of the class for fluent interface. """ if len(args) == 1 and isinstance(args[0], (int, float)): padding = args[0] elif kwargs: padding = {} for key in ['left', 'right', 'bottom', 'top']: if key in kwargs: padding[key] = kwargs[key] else: raise ValueError( 'Invalid arguments. Use one of the following formats:\n' '- set_padding(5): Uniform padding for all sides.\n' '- set_padding(left=10, top=15): Specify individual sides.\n' '- set_padding(left=10, top=15, right=20, bottom=25): Full padding definition.' ) self.instance.send(self.id, 'setPadding', {'padding': padding}) return self
[docs] def set_background_color(self, color: ColorInput | None): """Set the background color of the chart. Args: color (Color): Color of the background. Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send(self.id, 'setBackgroundFillStyle', {'color': color}) return self
[docs] def get_background_color(self) -> dict: """Get chart background color. Returns: dict with 'color', 'colorHex', 'colorRgb'. """ return self.instance.get(self.id, 'getChartBackgroundColor', {})
[docs] def set_background_stroke(self, thickness: int | float, color: ColorInput | None = None): """Set the background stroke style of the chart. Args: thickness (int | float): Thickness of the stroke. color (Color): The color of the stroke. Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send( self.id, 'setBackgroundStrokeStyle', {'thickness': thickness, 'color': color}, ) return self
[docs] def add_legend(self, **options: Unpack[LegendOptions]) -> Legend: """Add a new user-managed legend to the chart. Args: **options: Legend configuration options including: visible (bool): Whether legend should be visible (default: True) position: Legend position (LegendPosition enum or custom position dict) title (str): Legend title title_font (dict): Title font settings title_fill_style: Title color/fill style orientation: Legend orientation (LegendOrientation.Horizontal/Vertical) render_on_top (bool): Whether to render legend on top of chart background_visible (bool): Whether background should be visible background_fill_style: Background fill style background_stroke_style: Background stroke style padding: Legend content padding margin_inner: Margin from chart to legend margin_outer: Margin from legend to chart edge entry_margin: Margin between legend entries auto_hide_threshold (float): Auto-hide threshold (0.0-1.0) add_entries_automatically (bool): Whether to add entries automatically (default: False for user legends) entries (dict): Default entry options Returns: New user-managed legend instance Examples: Basic user-managed legend >>> legend = chart.add_legend(title="Custom Legend") Positioned legend >>> legend = chart.add_legend( ... position='TopRight', ... orientation='Horizontal', ... background_visible=True ) """ legend = Legend(self, is_user_legend=True) legend_id = str(uuid.uuid4()) legend.id = legend_id self.instance.send(legend_id, 'addLegend', {'chart': self.id}) if options: legend.set_options(**options) return legend
[docs] def add_textbox( self, text: str = None, x: int = None, y: int = None, position_scale: str = 'axis', ): """Add text box to the chart. Args: text (str): Text of the text box. x (int): X position in percentages (0-100). y (int): Y position in percentages (0-100). position_scale (str): "percentage" | "pixel" | "axis" Returns: Reference to Text Box class. """ return TextBox(chart=self, text=text, x=x, y=y, position_scale=position_scale)
textbox = add_textbox
[docs] def add_pointable_textbox( self, text: str = None, x: int = None, y: int = None, position_scale: str = 'axis', ): """Add pointable text box to the chart. Args: text (str): Text of the text box. x (int): X position. y (int): Y position. position_scale (str): "percentage" | "pixel" | "axis" Returns: Reference to PointableTextBox class. """ return PointableTextBox( chart=self, text=text, x=x, y=y, position_scale=position_scale, )
pointable_textbox = add_pointable_textbox
[docs] def add_checkbox( self, text: str = None, x: int = None, y: int = None, position_scale: str = 'axis', ): """Add checkbox to the chart. Args: text (str): Label text of the checkbox. x (int): X position. y (int): Y position. position_scale (str): "percentage" | "pixel" | "axis" Returns: Reference to CheckBox class. """ return CheckBox( chart=self, text=text, x=x, y=y, position_scale=position_scale, )
checkbox = add_checkbox
[docs] def add_buttonbox( self, text: str = None, x: int = None, y: int = None, position_scale: str = 'axis', ): """Add button box to the chart. Args: text (str): Label text of the button. x (int): X position. y (int): Y position. position_scale (str): "percentage" | "pixel" | "axis" Returns: Reference to ButtonBox class. """ return ButtonBox( chart=self, text=text, x=x, y=y, position_scale=position_scale, )
buttonbox = add_buttonbox
[docs] class TitleMethods(Chart):
[docs] def set_title(self, title: str): """Set text of Chart title. Args: title (str): Chart title as a string. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setTitle', {'title': title}) return self
[docs] def hide_title(self): """Hide title and remove padding around it. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'hideTitle') return self
[docs] def set_title_color(self, color: ColorInput | None): """Set color of Chart title. Args: color (Color): Color of the title. Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send(self.id, 'setTitleColor', {'color': color}) return self
[docs] def set_title_font( self, size: int | float, family: str = 'Segoe UI, -apple-system, Verdana, Helvetica', style: str = 'normal', weight: str = 'normal', ): """Set font of Chart title. Args: size (int | float): CSS font size. For example, 16. family (str): CSS font family. For example, 'Arial, Helvetica, sans-serif'. weight (str): CSS font weight. For example, 'bold'. style (str): CSS font style. For example, 'italic' Returns: The instance of the class for fluent interface. """ self.instance.send( self.id, 'setTitleFont', {'family': family, 'size': size, 'weight': weight, 'style': style}, ) return self
[docs] def set_title_margin(self, *args, **kwargs: Unpack[PaddingKwargs]): """Specifies padding after chart title. Args: *args: A single numeric value (int or float) for uniform padding on all sides. **kwargs: Optional named arguments to specify padding for individual sides: - `left` (int or float): Padding for the left side. - `right` (int or float): Padding for the right side. - `top` (int or float): Padding for the top side. - `bottom` (int or float): Padding for the bottom side. Examples: - `set_title_margin(5)`: Sets uniform padding for all sides (integer or float). - `set_title_margin(left=10, top=15)`: Sets padding for specific sides only. - `set_title_margin(left=10, top=15, right=20, bottom=25)`: Fully define padding for all sides. Returns: The instance of the class for fluent interface. """ if len(args) == 1 and isinstance(args[0], (int, float)): padding = args[0] elif kwargs: padding = {} for key in ['left', 'right', 'bottom', 'top']: if key in kwargs: padding[key] = kwargs[key] else: raise ValueError( 'Invalid arguments. Use one of the following formats:\n' '- set_title_margin(5): Uniform margin for all sides.\n' '- set_title_margin(left=10, top=15): Specify individual sides.\n' '- set_title_margin(left=10, top=15, right=20, bottom=25): Full margin definition.' ) self.instance.send(self.id, 'setTitleMargin', {'margin': padding}) return self
[docs] def set_title_rotation(self, degrees: int | float): """Set rotation of Chart title. Args: degrees (int | float): Rotation in degrees. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setTitleRotation', {'value': degrees}) return self
[docs] def set_title_effect(self, enabled: bool = True): """Set theme effect enabled on component or disabled. Args: enabled (bool): Theme effect enabled. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setTitleEffect', {'enabled': enabled}) return self
[docs] class ChartWithXYAxis(Chart): def __init__(self): self.default_x_axis = DefaultAxis(self, 'x') self.default_y_axis = DefaultAxis(self, 'y')
[docs] def get_default_x_axis(self) -> Axis: """Get the reference to the default x-axis of the chart. Returns: Reference to Axis class. """ return self.default_x_axis
[docs] def get_default_y_axis(self) -> Axis: """Get the reference to the default y-axis of the chart. Returns: Reference to Axis class. """ return self.default_y_axis
[docs] def synchronize_axis_intervals(self, axis_array: list[Axis]): """Convenience function for synchronizing the intervals of n amount of ´Axis´. Args: axis_array (list[Axis]): List of Axis to synchronize. Returns: The instance of the class for fluent interface. """ axis_array = [axis.id for axis in axis_array] self.instance.send(self.id, 'synchronizeAxes', {'axes': axis_array}) return self
[docs] class ChartWithXYZAxis(Chart): def __init__(self): self.default_x_axis = DefaultAxis3D(self, 'x') self.default_y_axis = DefaultAxis3D(self, 'y') self.default_z_axis = DefaultAxis3D(self, 'z')
[docs] def get_default_x_axis(self) -> DefaultAxis3D: """Get the reference to the default x-axis of the chart. Returns: Reference to Axis3D class. """ return self.default_x_axis
[docs] def get_default_y_axis(self) -> DefaultAxis3D: """Get the reference to the default y-axis of the chart. Returns: Reference to Axis3D class. """ return self.default_y_axis
[docs] def get_default_z_axis(self) -> DefaultAxis3D: """Get the reference to the default z-axis of the chart. Returns: Reference to Axis3D class. """ return self.default_z_axis
[docs] class ChartWithSeries(Chart): def __init__(self, instance: Instance): Chart.__init__(self, instance) self.series_list = [] self._auto_color_index_counter = 0 def _resolve_auto_color_index(self, automatic_color_index: int | None): """Return the user-specified automatic_color_index or generate a new one.""" if automatic_color_index is not None: return automatic_color_index idx = self._auto_color_index_counter self._auto_color_index_counter += 1 return idx
[docs] def set_series_background_effect(self, enabled: bool = True): """Set theme effect enabled on component or disabled. Args: enabled (bool): Theme effect enabled. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setSeriesBackgroundEffect', {'enabled': enabled}) return self
[docs] def set_series_background_color(self, color: ColorInput | None): """Set the color of chart series background. Args: color (Color): Color of the series background. Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send(self.id, 'setSeriesBackgroundFillStyle', {'color': color}) return self
[docs] def get_series_background_color(self) -> dict: """Get series-background color of the chart. Returns: dict with 'color' (uint32 RGBA), 'colorHex' (#rrggbbaa), 'colorRgb' (rgb/rgba). Notes: Call this in live mode, e.g. ``chart.open(live=True)`` """ return self.instance.get(self.id, 'getSeriesBackgroundColor', {})
[docs] def set_series_background_stroke(self, thickness: int | float, color: ColorInput | None = None): """ Set the stroke (border) of the Series Background (plot area). Use thickness=0 or color=None/'transparent' to hide it. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send( self.id, 'setSeriesBackgroundStrokeStyle', {'thickness': thickness, 'color': color}, ) return self
[docs] def set_engine_background_color(self, color: ColorInput | None): """ Set the background color of the chart's ENGINE (canvas behind the chart). Use 'transparent' or None to hide. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send(self.id, 'setEngineBackgroundFillStyle', {'color': color}) return self
[docs] class ChartWithLUT(Chart):
[docs] def set_lookup_table( self, steps: list[dict[str, any]], interpolate: bool = True, percentage_values: bool = False, ): """Attach lookup table (LUT) to fill the slices with Colors based on value. Args: steps (list[dict]): List of {"value": number, "color": Color, 'label': 'Label'} dictionaries. interpolate (bool): Whether color interpolation is used percentage_values (bool): Whether values represent percentages or explicit values. Returns: The instance of the class for fluent interface. """ steps = [dict(s, color=convert_color_to_hex(s['color'])) if 'color' in s else s for s in steps] self.instance.send( self.id, 'setLUT', { 'steps': steps, 'interpolate': interpolate, 'percentageValues': percentage_values, }, ) return self
[docs] class ChartWithLabelStyling(Chart):
[docs] def set_label_formatter(self, formatter: str = 'NamePlusValue'): """Set formatter of Slice Labels. Args: formatter: "Name" | "NamePlusValue" | "NamePlusRelativeValue" Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setLabelFormatter', {'formatter': formatter}) return self
[docs] def set_label_color(self, color: ColorInput | None): """Set the color of Slice Labels. Args: color (Color): Color of the labels. Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ color = convert_color_to_hex(color) if color is not None else None self.instance.send(self.id, 'setLabelColor', {'color': color}) return self
[docs] def set_label_font( self, size: int | float, family: str = 'Segoe UI, -apple-system, Verdana, Helvetica', style: str = 'normal', weight: str = 'normal', ): """Set font of Slice Labels. Args: size (int | float): CSS font size. For example, 16. family (str): CSS font family. For example, 'Arial, Helvetica, sans-serif'. weight (str): CSS font weight. For example, 'bold'. style (str): CSS font style. For example, 'italic' Returns: The instance of the class for fluent interface. """ self.instance.send( self.id, 'setLabelFont', {'family': family, 'size': size, 'weight': weight, 'style': style}, ) return self
[docs] def set_label_effect(self, enabled: bool): """Set theme effect enabled on label or disabled. A theme can specify an Effect to add extra visual oomph to chart applications, like Glow effects around data or other components. Args: enabled (bool): Theme effect enabled. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setLabelEffect', {'enabled': enabled}) return self
[docs] def set_slice_effect(self, enabled: bool): """Set theme effect enabled on slice or disabled. A theme can specify an Effect to add extra visual oomph to chart applications, like Glow effects around data or other components. Args: enabled (bool): Theme effect enabled. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setSliceEffect', {'enabled': enabled}) return self
[docs] def set_slice_colors(self, color_list: list[any]): """Set the colors of all slices at once. Args: color_list (list[Color]): list of Colors. The length must match the current number of slices! Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ hex_array = [] for color in color_list: hex_array.append(convert_color_to_hex(color)) self.instance.send(self.id, 'setSliceFuncFillStyle', {'hexArray': hex_array}) return self
[docs] def set_slice_sorter(self, sorter: str): """Define the sorting logic for slices. Args: sorter (str): "name" | "valueAscending" | "valueDescending" | "none" Returns: The instance of the class for fluent interface. """ slice_sorters = ('name', 'valueAscending', 'valueDescending', 'none') if sorter not in slice_sorters: raise ValueError(f"Expected sorter to be one of {slice_sorters}, but got '{sorter}'.") self.instance.send(self.id, 'setSliceSorter', {'sorter': sorter}) return self
[docs] def set_label_connector_style(self, style: str, thickness: int | float, color: ColorInput | None = None): """Set style of Label connector lines. Args: style (str): "solid" | "dashed" | "empty" thickness (int | float): Thickness of the connector line. color (Color): Color of the connector line. Use 'transparent' or None to hide. Returns: The instance of the class for fluent interface. """ color = convert_color_to_hex(color) if color is not None else None styles = ('solid', 'dashed', 'empty') if style not in styles: raise ValueError(f"Expected style to be one of {styles}, but got '{style}'.") self.instance.send( self.id, 'setLabelConnectorStyle', {'style': style, 'thickness': thickness, 'color': color}, ) return self
[docs] def get_slice_color(self, category: str) -> dict: """Get the fill color of a slice (Pie / Funnel / Pyramid). Args: category: Slice name. Returns: dict with 'color', 'colorHex', 'colorRgb'. Notes: Call this in live mode, e.g. ``chart.open(live=True)`` """ return self.instance.get(self.id, 'getSliceFillStyle', {'category': category})
[docs] class BackgroundChartStyle(Chart):
[docs] def set_chart_background_image( self, source: str, fit_mode: str = 'Stretch', surrounding_color=None, source_missing_color=None, ): """ Set the chart background image. Args: source (str): The image source. This can be: - A URL (remote image). - A local file path. - An already Base64-encoded image string. fit_mode (str, optional): Fit mode for the image. Options: - "Stretch" (default) - "Fill" - "Fit" - "Tile" - "Center" surrounding_color (Color, optional): Color for areas outside the image. source_missing_color (Color, optional): Color when the image fails to load. Returns: self: The instance of the class for method chaining. Raises: ValueError: If the source is invalid. Example: >>> chart.set_chart_background_image("D:/path/to/local_image.png") >>> chart.set_chart_background_image("https://example.com/image.jpg") """ if not source: raise ValueError('Image source is required.') if not source.startswith('data:'): source = convert_to_base64(source) args = { 'source': source, 'fitMode': fit_mode, 'surroundingColor': convert_color_to_hex(surrounding_color) if surrounding_color else None, 'sourceMissingColor': convert_color_to_hex(source_missing_color) if source_missing_color else None, } for fit_mode_option in ['Stretch', 'Fill', 'Fit', 'Tile', 'Center']: if fit_mode_option.lower() == fit_mode.lower(): args['fitMode'] = fit_mode_option break self.instance.send(self.id, 'setChartBackgroundImage', args) return self
[docs] def set_chart_background_video( self, video_source: str, fit_mode: str = 'fit', surrounding_color: ColorInput | None = None, source_missing_color: ColorInput | None = None ): """ Sets the chart background to a video. Args: video_source (str): Path to the video file (MP4 or WEBM). fit_mode (str): Fit mode ('Fit', 'Stretch', 'Fill', 'Center', 'Tile'). surrounding_color (Color, optional): Color for areas outside the video. source_missing_color (Color, optional): Color when video fails to load. Returns: The instance of the class for method chaining. Example: >>> chart.set_chart_background_video("D:/path/to/local_video.mp4") >>> chart.set_chart_background_video("https://example.com/video.mp4") """ if not video_source: raise ValueError('Video source is required.') video_data_uri = convert_to_base64(video_source) surrounding_color = convert_color_to_hex(surrounding_color) if surrounding_color is not None else None source_missing_color = convert_color_to_hex(source_missing_color) if source_missing_color is not None else None args = { 'videoSource': video_data_uri, 'fitMode': fit_mode, 'surroundingColor': surrounding_color if surrounding_color else None, 'sourceMissingColor': source_missing_color if source_missing_color else None, } for fit_mode_option in ['Stretch', 'Fill', 'Fit', 'Tile', 'Center']: if fit_mode_option.lower() == fit_mode.lower(): args['fitMode'] = fit_mode_option break self.instance.send(self.id, 'setChartBackgroundVideo', args) return self
[docs] def set_series_background_image( self, source: str, fit_mode: str = 'Stretch', surrounding_color=None, source_missing_color=None, ): """ Set the series background image. Args: source (str): The image source. This can be: - A URL (remote image). - A local file path. - An already Base64-encoded image string. fit_mode (str, optional): Fit mode for the image. Options: - "Stretch" (default) - "Fill" - "Fit" - "Tile" - "Center" surrounding_color (Color, optional): Color for areas outside the image. source_missing_color (Color, optional): Color when the image fails to load. Returns: self: The instance of the class for method chaining. Raises: ValueError: If the source is invalid. Example: >>> chart.set_series_background_image("D:/path/to/local_image.png") >>> chart.set_series_background_image("https://example.com/image.jpg") """ if not source: raise ValueError('Image source is required.') if not source.startswith('data:'): source = convert_to_base64(source) args = { 'source': source, 'fitMode': fit_mode, 'surroundingColor': convert_color_to_hex(surrounding_color) if surrounding_color else None, 'sourceMissingColor': convert_color_to_hex(source_missing_color) if source_missing_color else None, } for fit_mode_option in ['Stretch', 'Fill', 'Fit', 'Tile', 'Center']: if fit_mode_option.lower() == fit_mode.lower(): args['fitMode'] = fit_mode_option break self.instance.send(self.id, 'setSeriesBackgroundImage', args) return self
[docs] def set_series_background_video( self, video_source: str, fit_mode: str = 'fit', surrounding_color: ColorInput | None = None, source_missing_color: ColorInput | None = None, ): """ Sets the series background to a video. Args: video_source (str): Path to the video file (MP4 or WEBM). fit_mode (str): Fit mode ('Fit', 'Stretch', 'Fill', 'Center', 'Tile'). surrounding_color (Color, optional): Color for areas outside the video. source_missing_color (Color, optional): Color when video fails to load. Returns: The instance of the class for method chaining. Example: >>> chart.set_series_background_video("D:/path/to/local_video.mp4") >>> chart.set_series_background_video("https://example.com/video.mp4") """ if not video_source: raise ValueError('Video source is required.') video_data_uri = convert_to_base64(video_source) surrounding_color = convert_color_to_hex(surrounding_color) if surrounding_color is not None else None source_missing_color = convert_color_to_hex(source_missing_color) if source_missing_color is not None else None args = { 'videoSource': video_data_uri, 'fitMode': fit_mode, 'surroundingColor': surrounding_color if surrounding_color else None, 'sourceMissingColor': source_missing_color if source_missing_color else None, } for fit_mode_option in ['Stretch', 'Fill', 'Fit', 'Tile', 'Center']: if fit_mode_option.lower() == fit_mode.lower(): args['fitMode'] = fit_mode_option break self.instance.send(self.id, 'setSeriesBackgroundVideo', args) return self
[docs] class FunnelPyramidLabelConnectorMethods:
[docs] def set_label_connector_gap_before_label(self, gap: int | float): """Set gap before label in label connector. Args: gap (int | float): Gap in pixels. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setLabelConnectorGapBeforeLabel', {'gap': gap}) return self
[docs] def set_label_connector_gap_before_slice(self, gap: int | float): """Set gap before slice in label connector. Args: gap (int | float): Gap in pixels. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setLabelConnectorGapBeforeSlice', {'gap': gap}) return self
[docs] def set_label_connector_length_after_slice(self, length: int | float): """Set length after slice in label connector. Args: length (int | float): Length in pixels. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setLabelConnectorLengthAfterSlice', {'length': length}) return self
[docs] def set_label_connector_min_length_before_slice(self, length: int | float): """Set minimum length before slice in label connector. Args: length (int | float): Minimum length in pixels. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setLabelConnectorMinLengthBeforeSlice', {'length': length}) return self
[docs] class ChartsWithCoordinateTransforms:
[docs] def translate_coordinate(self, coordinate: dict, target: str, source: str = None): """Translate coordinates between client (browser) and relative (component) systems. Args: coordinate: Dict with 'x'/'y' (relative) or 'clientX'/'clientY' (client) target: 'relative' | 'client' source: 'relative' | 'client' (auto-detected if None) Returns: Dict with translated coordinates Examples: >>> # Client to relative (source auto-detected) >>> loc = chart.translate_coordinate({'clientX': 500, 'clientY': 300}, target='relative') >>> print(f"Relative: x={loc['x']}, y={loc['y']}") >>> # Relative to client >>> loc = dashboard.translate_coordinate({'x': 400, 'y': 250}, target='client', source='relative') >>> print(f"Client: x={loc['clientX']}, y={loc['clientY']}") """ if source is None: source = 'client' if 'clientX' in coordinate else 'relative' return self.instance.get(self.id, 'translateCoordinateGeneral', {'coordinate': coordinate, 'source': source, 'target': target})
[docs] class ChartsWithAddEventListener:
[docs] def add_event_listener( self, event: str, handler: callable | None = None, xy_chart=None, throttle_ms: int = 0, once: bool = False, target: str = 'auto', ) -> str: """ Add event and (optionally) bind a ChartXY overlay to stay in sync with a Map/Bar view. Args: event : str Event name emitted by the target. Common options include: - Interaction: 'click', 'pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave', 'dblclick' - Cursor: 'cursortargetchange' (reports cursor hits / mouse location) - Lifecycle/Layout: 'ready', 'layoutchange' - Map/Bar view: 'viewchange' (latitude/longitude + margins) handler : Python callback receiving event data xy_chart : ChartXY | None, keyword-only When `event == 'viewchange'`, pass a `ChartXY` here to keep its axes and padding bound to the Map/Bar view. Ignored for other events. throttle_ms : Minimum delay between callbacks in milliseconds once : bool, default False, keyword-only If True, listener removes itself after first trigger target : {'auto','seriesBackground','background','title','axisXTitle','axisYTitle','chart'}, default 'auto' Which DOM element to attach to: - 'auto' : For mouse/pointer events, uses plot area (`seriesBackground`) if available, otherwise the chart. - 'seriesBackground': Plot area (inside axes) — best for data-space clicks/moves. - 'background' : Chart background (outside axes). - 'title' : Chart title element. - 'axisXTitle' : X-axis title element. - 'axisYTitle' : Y-axis title element. - 'axisZTitle' : Z-axis title element (3D charts only). - 'chart' : Chart object itself (e.g., 'layoutchange', 'ready', or chart-level cursor events). Returns: `callback_id` that identifies the registered handler (empty string if no `handler` was supplied). Examples: # 1) Clicks in the plot area of a ChartXY >>> def on_click(ev): print('[click]', ev) >>> xy.add_event_listener('click', handler=on_click, target='seriesBackground') # 2) Keep an overlayed XY chart glued to a Map view (pan/zoom/resize) >>> map_chart.add_event_listener('viewchange', xy_chart=xy_overlay) # 3) Listen cursor hits (hover) at chart level >>> def on_cursor(ev): print(ev.get('hits')) >>> xy.add_event_listener('cursortargetchange', handler=on_cursor, target='chart', throttle_ms=50) # 4) Run code when a chart signals it is ready >>> def on_ready(_): print('chart ready') >>> map_chart.add_event_listener('ready', handler=on_ready, target='chart') """ callback_id = str(uuid.uuid4()).split('-')[0] if handler else '' if handler is not None: self.instance.event_handlers[callback_id] = handler self.instance.send( self.id, 'addEventListener', { 'event': event, 'callbackId': callback_id or None, 'xyId': getattr(xy_chart, 'id', None), 'throttleMs': int(throttle_ms) if throttle_ms else 0, 'options': {'once': bool(once)}, 'target': target, }, ) return callback_id
[docs] class GeneralGetMethods:
[docs] def get_title(self) -> str | None: """Get text of Chart title. Returns: The instance of the class for fluent interface. Notes: Call this in live mode, e.g. ``chart.open(live=True)`` """ return self.instance.get(self.id, 'getTitle', {})
[docs] def get_title_effect(self) -> bool: """Get whether theme effect is enabled on axis title. A theme can specify an Effect to add extra visual elements to chart applications, like Glow effects around data or other components. Returns: bool: True if theme effect is enabled, False otherwise. Notes: Call this in live mode, e.g. ``chart.open(live=True)`` Examples: >>> effect_enabled = chart.get_title_effect() """ return self.instance.get(self.id, 'getTitleEffect', {})
[docs] def get_title_fill_style(self) -> str | None: """Get chart title fill style (color). Returns: str | None: Color as rgba string, or None if transparent. Notes: Call this in live mode, e.g. ``chart.open(live=True)`` Examples: >>> color = chart.get_title_fill_style() """ return self.instance.get(self.id, 'getTitleFillStyle', {})
[docs] def get_title_font(self) -> dict: """Get font settings of chart title. Returns: dict: Font settings with keys: - size (float): Font size - family (str): Font family - weight (str): Font weight (e.g., 'normal', 'bold') - style (str): Font style (e.g., 'normal', 'italic') Notes: Call this in live mode, e.g. ``chart.open(live=True)`` Examples: >>> font = chart.get_title_font() >>> print(f"Size: {font['size']}, Family: {font['family']}") """ return self.instance.get(self.id, 'getTitleFont', {})
[docs] def get_title_rotation(self) -> float: """Get rotation of chart title. Returns: float: Rotation in degrees. Notes: Call this in live mode, e.g. ``chart.open(live=True)`` Examples: >>> rotation = chart.get_title_rotation() >>> print(f"Title rotation: {rotation} degrees") """ return self.instance.get(self.id, 'getTitleRotation', {})
[docs] def get_size_pixels(self): """Get size of control as pixels. Returns: Control size in pixels. Notes: Call this in live mode, e.g. ``chart.open(live=True)`` """ result = self.instance.get(self.id, 'getSizePixels', {}) return result
[docs] def get_animations_enabled(self) -> bool: """Get animations disable/enable state.""" return self.instance.get(self.id, 'getAnimationsEnabled', {})
[docs] def get_background_fill_style(self) -> str | None: """Get chart background fill style color as rgba string (or None if transparent/unavailable).""" return self.instance.get(self.id, 'getBackgroundFillStyle', {})
[docs] def get_background_stroke_style(self) -> dict | None: """Get chart background stroke style. Returns: dict | None: {'thickness': number|None, 'color': 'rgba(...)'|None} """ return self.instance.get(self.id, 'getBackgroundStrokeStyle', {})
[docs] def get_is_in_view(self) -> bool: """True when chart/panel is considered to be in browser viewport.""" return self.instance.get(self.id, 'getIsInView', {})
[docs] def get_minimum_size(self) -> dict | None: """Get minimum size of the panel. Returns: dict | None: {'x': number, 'y': number} or None """ return self.instance.get(self.id, 'getMinimumSize', {})
[docs] def get_padding(self) -> dict: """Get padding around chart in pixels. Returns: dict: Margin-like object (e.g. {'top':..,'right':..,'bottom':..,'left':..}) """ return self.instance.get(self.id, 'getPadding', {})
[docs] def get_theme(self) -> dict: """Get the Theme currently being used. Returns: dict: Theme object. """ return self.instance.get(self.id, 'getTheme', {})
[docs] def get_title_margin(self) -> dict: """Get padding/margin after chart title. Returns: dict: Margin-like object (e.g. {'top':..,'right':..,'bottom':..,'left':..}) """ return self.instance.get(self.id, 'getTitleMargin', {})
[docs] def get_title_size(self) -> dict: """Get chart title size in pixels. Returns: dict: {'x': number, 'y': number} """ return self.instance.get(self.id, 'getTitleSize', {})
[docs] class CursorBehaviorMixin: """Shared cursor behavior utilities for charts with built-in cursors.""" _cursor_modes = ( 'disabled', 'show-all', 'show-all-interpolated', 'show-nearest', 'show-nearest-interpolated', 'show-pointed', 'show-pointed-interpolated', ) _custom_cursor_callback_id: str | None = None _cursor_formatter_callback_id: str | None = None _cursor_formatter_handler: Callable[[Any], Any] | None = None
[docs] def set_cursor_mode(self, mode: str | None): """Set built-in cursor mode. Args: mode (str | None): One of "disabled", "show-nearest", "show-nearest-interpolated", "show-pointed", "show-pointed-interpolated", "show-all", "show-all-interpolated". ``None`` is treated as ``"disabled"``. Returns: The instance of the class for fluent interface. """ if mode is None: mode_normalized = 'disabled' elif isinstance(mode, str): mode_normalized = mode.strip().lower() else: raise ValueError('mode must be a string or None.') if mode_normalized not in self._cursor_modes: raise ValueError(f"Expected mode to be one of {self._cursor_modes}, but got '{mode}'.") self.instance.send(self.id, 'setCursorMode', {'mode': mode_normalized}) return self
[docs] def get_cursor_mode(self) -> str: """Get active cursor mode (returns 'disabled' when cursor is off).""" mode = self.instance.get(self.id, 'getCursorMode', {}) if isinstance(mode, str) and mode: return mode return 'disabled'
[docs] def get_cursor(self): """Returns the managed cursor instance.""" return self.set_cursor()
[docs] def set_cursor_dynamic_behavior( self, match_point_marker_shape: bool | None = None, point_marker_fill: str | None = None, point_marker_stroke: dict | None = None, point_marker_size: int | float | tuple[float, float] | dict | None = None, ): """ Configure automatic cursor styling. Args: match_point_marker_shape (bool, optional): Match marker shape to hovered series marker. point_marker_fill (str, optional): Hex/RGB color or ``'match-data'`` to use series color. point_marker_stroke (dict, optional): {'color': '#fff'|'match-data', 'thickness': float} point_marker_size (float | tuple | dict, optional): Uniform size or {'x','y'} mapping. Returns: The instance of the class for fluent interface. """ params = {} if match_point_marker_shape is None and point_marker_fill is None and point_marker_stroke is None and point_marker_size is None: params['enabled'] = False self.instance.send(self.id, 'setCursorDynamicBehavior', params) return self params['enabled'] = True if match_point_marker_shape is not None: params['matchShape'] = bool(match_point_marker_shape) if point_marker_fill is not None: if isinstance(point_marker_fill, str) and point_marker_fill.replace('_', '-').lower() == 'match-data': params['fillMode'] = 'data' else: params['fillMode'] = 'solid' params['fillColor'] = convert_color_to_hex(point_marker_fill) stroke_opts = point_marker_stroke or {} if stroke_opts: stroke_color = stroke_opts.get('color') if stroke_color is None and stroke_opts.get('thickness') is not None: stroke_color = 'match-data' if isinstance(stroke_color, str) and stroke_color.replace('_', '-').lower() == 'match-data': params['strokeMode'] = 'data' elif stroke_color is not None: params['strokeMode'] = 'solid' params['strokeColor'] = convert_color_to_hex(stroke_color) if stroke_opts.get('thickness') is not None: params['strokeThickness'] = stroke_opts['thickness'] if point_marker_size is not None: if isinstance(point_marker_size, (int, float)): params['size'] = {'x': point_marker_size, 'y': point_marker_size} elif isinstance(point_marker_size, (tuple, list)): params['size'] = {'x': point_marker_size[0], 'y': point_marker_size[1]} else: params['size'] = point_marker_size self.instance.send(self.id, 'setCursorDynamicBehavior', params) return self
[docs] def set_custom_cursor(self, handler: Callable[[dict], Any] | None = None, throttle_ms: int = 0): """ Replace the built-in cursor with a custom handler. Args: handler (callable, optional): Receives the same payload as the chart event cursortargetchange. throttle_ms (int, optional): Minimum delay between handler invocations. """ if self._custom_cursor_callback_id: self.instance.event_handlers.pop(self._custom_cursor_callback_id, None) self._custom_cursor_callback_id = None if handler is None: self.instance.send(self.id, 'setCustomCursor', {'callbackId': None}) return self callback_id = str(uuid.uuid4()).split('-')[0] self.instance.event_handlers[callback_id] = handler self._custom_cursor_callback_id = callback_id self.instance.send( self.id, 'setCustomCursor', {'callbackId': callback_id, 'throttleMs': int(throttle_ms) if throttle_ms else 0}, ) return self
[docs] def set_cursor_formatting(self, formatter: Callable[[dict], Any] | None): """ Set a custom formatter for the cursor tooltip. Args: formatter (callable | None): Python callable returning ``ResultTable`` rows (list of lists) or None to reset to the default cursor formatting. """ if self._cursor_formatter_callback_id: self.instance.event_handlers.pop(self._cursor_formatter_callback_id, None) self._cursor_formatter_callback_id = None self._cursor_formatter_handler = None if formatter is None: self.instance.send(self.id, 'setCursorFormatting', {'callbackId': None}) return self callback_id = str(uuid.uuid4()).split('-')[0] self.instance.event_handlers[callback_id] = formatter self._cursor_formatter_callback_id = callback_id self._cursor_formatter_handler = formatter self.instance.send(self.id, 'setCursorFormatting', {'callbackId': callback_id}) return self
[docs] def get_cursor_formatting(self): """Return the currently registered cursor formatter callable (if any).""" return self._cursor_formatter_handler
[docs] class ManualCursorXY(CursorXY): def __init__(self, chart, cursor_id: str): super().__init__(chart, target_id=cursor_id)
[docs] def dispose(self): self.instance.send(self._target_id, 'dispose', {}) return self
[docs] class ManualCursor2D(Cursor2D): def __init__(self, chart, cursor_id: str): super().__init__(chart, target_id=cursor_id)
[docs] def dispose(self): self.instance.send(self._target_id, 'dispose', {}) return self
[docs] class ManualCursor3D(Cursor3D): def __init__(self, chart, cursor_id: str): super().__init__(chart, target_id=cursor_id)
[docs] def dispose(self): self.instance.send(self._target_id, 'dispose', {}) return self
[docs] class ChartWithCursorXY(CursorBehaviorMixin): _cursor_obj: 'CursorXY | None' = None
[docs] def set_cursor(self) -> 'CursorXY': if self._cursor_obj is None: from lightningchart.ui.cursor import CursorXY self._cursor_obj = CursorXY(self) self.instance.send(self.id, 'cursorInit', {}) return self._cursor_obj
[docs] def add_cursor(self) -> 'ManualCursorXY': """ Create a manual cursor (independent from the built-in cursor modes). Notes: - Manual cursors do NOT use chart.set_cursor_mode(...) hover/nearest logic. You control their position via cursor.set_position(...). - The result table is not auto-populated for manual cursors. To display it, you must provide content via cursor.set_result_table(content=...), e.g. content=[["X", "60"], ["Y", "60"]]. Otherwise the result table may remain empty/invisible even if visible=True. Returns: ManualCursorXY: The created manual cursor instance. Examples: >>> c = chart.add_cursor() >>> c.set_position(60, 60) >>> c.set_result_table(visible=True, content=[["X", "60"], ["Y", "60"]]) """ cursor_id = str(uuid.uuid4()).split('-')[0] self.instance.send(self.id, 'addCursor', {'cursorId': cursor_id}) return ManualCursorXY(self, cursor_id)
[docs] def set_cursor_enabled_during_axis_animation(self, enabled: bool): """Disable/Enable Cursor during Axis Animations. Axis Animations are Axis Scale changes that are animated, such as Zooming and Scrolling done by using API (such as Axis.set_interval) or by using the mouse to click & drag on the Chart. Args: enabled (bool): Boolean value to enable or disable Cursor during Axis Animations. Returns: The instance of the class for fluent interface. """ self.instance.send(self.id, 'setCursorEnabledDuringAxisAnimation', {'enabled': enabled}) return self
[docs] def get_cursor_enabled_during_axis_animation(self) -> bool: """Get whether the built-in cursor is enabled during axis animations (pan/zoom). Returns: bool: True if enabled during axis animations, False otherwise. """ return self.instance.get(self.id, 'getCursorEnabledDuringAxisAnimation', {})
[docs] class ChartWithCursor2D(CursorBehaviorMixin): _cursor_obj: 'Cursor2D | None' = None
[docs] def set_cursor(self) -> 'Cursor2D': if self._cursor_obj is None: from lightningchart.ui.cursor import Cursor2D self._cursor_obj = Cursor2D(self) self.instance.send(self.id, 'cursorInit', {}) return self._cursor_obj
[docs] def add_cursor(self) -> 'ManualCursor2D': """Create a manual cursor.""" cursor_id = str(uuid.uuid4()).split('-')[0] self.instance.send(self.id, 'addCursor', {'cursorId': cursor_id}) return ManualCursor2D(self, cursor_id)
[docs] class ChartWithCursor3D(CursorBehaviorMixin): _cursor_obj: 'Cursor3D | None' = None
[docs] def set_cursor(self) -> 'Cursor3D': if self._cursor_obj is None: from lightningchart.ui.cursor import Cursor3D self._cursor_obj = Cursor3D(self) self.instance.send(self.id, 'cursorInit', {}) return self._cursor_obj
[docs] def add_cursor(self) -> 'ManualCursor3D': """Create a manual cursor.""" cursor_id = str(uuid.uuid4()).split('-')[0] self.instance.send(self.id, 'addCursor', {'cursorId': cursor_id}) return ManualCursor3D(self, cursor_id)