utils.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Copyright 2021-2024 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. from __future__ import annotations
  12. import typing as t
  13. from abc import ABC, abstractmethod
  14. import numpy as np
  15. from .._warnings import _warn
  16. if t.TYPE_CHECKING:
  17. import pandas as pd
  18. class Decimator(ABC):
  19. """Base class for decimating chart data.
  20. *Decimating* is the term used to name the process of reducing the number of
  21. data points displayed in charts while retaining the overall shape of the traces.
  22. `Decimator` is a base class that does decimation on data sets.
  23. Taipy GUI comes out-of-the-box with several implementation of this class for
  24. different use cases.
  25. """
  26. _CHART_MODES: t.List[str] = []
  27. def __init__(self, threshold: t.Optional[int], zoom: t.Optional[bool]) -> None:
  28. """Initialize a new `Decimator`.
  29. Arguments:
  30. threshold (Optional[int]): The minimum amount of data points before the
  31. decimator class is applied.
  32. zoom (Optional[bool]): set to True to reapply the decimation
  33. when zoom or re-layout events are triggered.
  34. """
  35. super().__init__()
  36. self.threshold = threshold
  37. self._zoom = zoom if zoom is not None else True
  38. def _is_applicable(self, data: t.Any, nb_rows_max: int, chart_mode: str):
  39. if chart_mode not in self._CHART_MODES:
  40. _warn(f"{type(self).__name__} is only applicable for {' '.join(self._CHART_MODES)}.")
  41. return False
  42. if self.threshold is None:
  43. if nb_rows_max < len(data):
  44. return True
  45. elif self.threshold < len(data):
  46. return True
  47. return False
  48. @abstractmethod
  49. def decimate(self, data: np.ndarray, payload: t.Dict[str, t.Any]) -> np.ndarray:
  50. """Decimate function.
  51. This method is executed when the appropriate conditions specified in the
  52. constructor are met. This function implements the algorithm that determines
  53. which data points are kept or dropped.
  54. Arguments:
  55. data (numpy.array): An array containing all the data points represented as
  56. tuples.
  57. payload (Dict[str, any]): additional information on charts that is provided
  58. at runtime.
  59. Returns:
  60. An array of Boolean mask values. The array should set True or False for each
  61. of its indexes where True indicates that the corresponding data point
  62. from *data* should be preserved, or False requires that this
  63. data point be dropped.
  64. """
  65. return NotImplementedError # type: ignore
  66. def _df_data_filter(
  67. dataframe: pd.DataFrame,
  68. x_column_name: t.Optional[str],
  69. y_column_name: str,
  70. z_column_name: str,
  71. decimator: Decimator,
  72. payload: t.Dict[str, t.Any],
  73. is_copied: bool,
  74. ):
  75. df = dataframe.copy() if not is_copied else dataframe
  76. if not x_column_name:
  77. index = 0
  78. while f"tAiPy_index_{index}" in df.columns:
  79. index += 1
  80. x_column_name = f"tAiPy_index_{index}"
  81. df[x_column_name] = df.index
  82. column_list = [x_column_name, y_column_name, z_column_name] if z_column_name else [x_column_name, y_column_name]
  83. points = df[column_list].to_numpy()
  84. mask = decimator.decimate(points, payload)
  85. return df[mask], is_copied
  86. def _df_relayout(
  87. dataframe: pd.DataFrame,
  88. x_column: t.Optional[str],
  89. y_column: str,
  90. chart_mode: str,
  91. x0: t.Optional[float],
  92. x1: t.Optional[float],
  93. y0: t.Optional[float],
  94. y1: t.Optional[float],
  95. is_copied: bool,
  96. ):
  97. if chart_mode not in ["lines+markers", "markers"]:
  98. return dataframe, is_copied
  99. # if chart data is invalid
  100. if x0 is None and x1 is None and y0 is None and y1 is None:
  101. return dataframe, is_copied
  102. df = dataframe.copy() if not is_copied else dataframe
  103. is_copied = True
  104. has_x_col = True
  105. if not x_column:
  106. index = 0
  107. while f"tAiPy_index_{index}" in df.columns:
  108. index += 1
  109. x_column = f"tAiPy_index_{index}"
  110. df[x_column] = df.index
  111. has_x_col = False
  112. df_filter_conditions = []
  113. # filter by x column by default
  114. if x0 is not None:
  115. df_filter_conditions.append(df[x_column] > x0)
  116. if x1 is not None:
  117. df_filter_conditions.append(df[x_column] < x1)
  118. # y column will be filtered only if chart_mode is not lines+markers (eg. markers)
  119. if chart_mode != "lines+markers":
  120. if y0 is not None:
  121. df_filter_conditions.append(df[y_column] > y0)
  122. if y1 is not None:
  123. df_filter_conditions.append(df[y_column] < y1)
  124. if df_filter_conditions:
  125. df = df.loc[np.bitwise_and.reduce(df_filter_conditions)]
  126. if not has_x_col:
  127. df.drop(x_column, axis=1, inplace=True)
  128. return df, is_copied