markers finished, subsecond precision supported

This commit is contained in:
David Brazda
2024-06-11 09:06:56 +02:00
parent 23fbf3a23d
commit 9bd4d089b5
2 changed files with 73 additions and 25 deletions

View File

@ -13,7 +13,7 @@ from .drawings import Box, HorizontalLine, RayLine, TrendLine, TwoPointDrawing,
from .topbar import TopBar from .topbar import TopBar
from .util import ( from .util import (
BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT, BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT,
LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, CROSSHAIR_MODE, LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, CROSSHAIR_MODE, MARKER_TYPE,
PRICE_SCALE_MODE, marker_position, marker_shape, js_data, PRICE_SCALE_MODE, marker_position, marker_shape, js_data,
) )
@ -274,12 +274,12 @@ class SeriesCommon(Pane):
self._set_interval(df) self._set_interval(df)
if not pd.api.types.is_datetime64_any_dtype(df['time']): if not pd.api.types.is_datetime64_any_dtype(df['time']):
df['time'] = pd.to_datetime(df['time']) df['time'] = pd.to_datetime(df['time'])
df['time'] = df['time'].astype('int64') // 10 ** 9 df['time'] = df['time'].astype('int64') / 10 ** 9 #removed integer divison // 10 ** 9 to keep subseconds precision
return df return df
def _series_datetime_format(self, series: pd.Series, exclude_lowercase=None): def _series_datetime_format(self, series: pd.Series, exclude_lowercase=None):
series = series.copy() series = series.copy()
series.index = self._format_labels(series, series.index, series.name, exclude_lowercase) series.index = self._format_labels(series, series.name, series.index, exclude_lowercase)
series['time'] = self._single_datetime_format(series['time']) series['time'] = self._single_datetime_format(series['time'])
return series return series
@ -325,36 +325,83 @@ class SeriesCommon(Pane):
def _update_markers(self): def _update_markers(self):
self.run_script(f'{self.id}.series.setMarkers({json.dumps(list(self.markers.values()))})') self.run_script(f'{self.id}.series.setMarkers({json.dumps(list(self.markers.values()))})')
def markers_set(self, markers_series: pd.Series, def markers_set(self, markers: Union[pd.Series, pd.DataFrame],
type: MARKER_TYPE = None,
col_name: Optional[str] = None,
position: MARKER_POSITION = 'below', position: MARKER_POSITION = 'below',
shape: MARKER_SHAPE = 'arrow_up', shape: MARKER_SHAPE = 'arrow_up',
color: str = '#2196F3',text: str = ''): color: str = '#2196F3', text: str = ''):
""" """
Creates multiple markers from pd series. Adds multiple markers from pd series or Dataframe
:param markers: A pandas Series with DateTimeIndex and boolean values. :param markers: A pandas Series or Dataframe with DateTimeIndex and boolean values.
The index should be DateTimeIndex and values should be True/False. The index should be DateTimeIndex and values should be True/False.
:param type: The type of the marker to quickly style entries or exits
:param col_name: The name of the column to use in case of DataFrame
:param position: The position of the marker.
:param shape: The shape of the marker.
:param color: The color of the marker.
:return: a list of marker ids. :return: a list of marker ids.
It refreshes the markers, deleting the current. It adds new markers to the chart. Existing markers will remain.
To delete markers use remove_marker() or clear_markers()
""" """
if not isinstance(markers_series, pd.Series): if type is not None:
#raise exception match type:
raise TypeError('Markers must be a pd.Series') case "entries":
series = self._series_datetime_format(markers_series, exclude_lowercase=self.name) position = "below"
marker_ids = [] shape = "arrow_up"
self.markers = {} color = "blue"
for timestamp, value in series.iteritems(): case "exits":
if value: position = "above"
marker_id = self.win._id_gen.generate() shape = "arrow_down"
self.markers[marker_id] = { color = "red"
"time": timestamp,
"position": marker_position(position), # Default position
"color": color, # Default color
"shape": marker_shape(shape), # Default shape
"text": text, # Default text
}
marker_ids.append(marker_id)
if isinstance(markers, pd.Series):
markers = markers.to_frame(name="markers")
markers = self._df_datetime_format(markers)
# Get the list of columns in the DataFrame - must be datetimeindex
#either with one column, or col_name is set
columns = markers.columns.tolist()
if "time" not in columns:
raise ValueError("Time column required in the dataframe.")
# If there are exactly two columns
if len(columns) == 2:
# Rename the non-"time" column to "value"
other_col = [col for col in columns if col != "time"][0]
markers.rename(columns={other_col: "value"}, inplace=True)
# If there are more than two columns
elif len(columns) > 2:
if col_name in columns:
# Keep "time" and col_name column and rename it to "value"
markers = markers[["time", col_name]]
markers.rename(columns={col_name: "value"}, inplace=True)
else:
raise ValueError("Specify a column name in the dataframe.")
else:
raise ValueError("No matching columns in the dataframe.")
marker_ids = []
#self.markers = {}
valid_rows = markers[markers['value']] # Filter rows where value is True
for timestamp in valid_rows['time']:
marker_id = self.win._id_gen.generate()
self.markers[marker_id] = {
"time": timestamp,
"position": marker_position(position), # Default position
"color": color, # Default color
"shape": marker_shape(shape), # Default shape
"text": text, # Default text
}
marker_ids.append(marker_id)
# Sort markers by time
self.markers = dict(sorted(self.markers.items(), key=lambda item: item[1]["time"]))
self._update_markers() self._update_markers()
return marker_ids return marker_ids

View File

@ -63,6 +63,7 @@ def js_json(d: dict):
def jbool(b: bool): return 'true' if b is True else 'false' if b is False else None def jbool(b: bool): return 'true' if b is True else 'false' if b is False else None
MARKER_TYPE = Literal['entries', 'exits']
LINE_STYLE = Literal['solid', 'dotted', 'dashed', 'large_dashed', 'sparse_dotted'] LINE_STYLE = Literal['solid', 'dotted', 'dashed', 'large_dashed', 'sparse_dotted']