from typing import Union, List, Tuple, Optional
from .vector import Vector
from .data import Data
from .datetime_utils import datetime_to_str
from ._private._well_dto import EmbeddedChannelDto, TimeSeriesReferenceChannelDto, ChannelAspectDto, ChannelType, DoubleValuesDto, TimeValuesDto, CrossReferenceChannelDto
from .plots_enum import PlotDataTypesEnum, SymbolAspectEnum, LineAspectEnum, PlotProperties, DrawingStyle
from .measure_enum import MeasureEnum
from .field_data_types_catalog import FieldDataTypesCatalog
from pydantic import ValidationError
[docs]
class PlotChannel:
"""
Build a PlotChannel dto for the :class:`Plot` object.
.. note:: Should not be instantiated directly.
"""
def __init__(self, field_id: str, data_types_catalog: FieldDataTypesCatalog, pane_name: Optional[str] = None):
self.__field_id: str = field_id
self.__data_types_catalog: FieldDataTypesCatalog = data_types_catalog
self.__dto: Optional[Union[EmbeddedChannelDto, TimeSeriesReferenceChannelDto, CrossReferenceChannelDto]] = None
self.__pane_name: Optional[str] = pane_name
def build_time_series(self, well_id: str, data: Data, properties: Optional[PlotProperties] = None) -> None:
""" Build plot channel from a time series
Parameters
----------
well_id:
Id of the well
data:
:class:`Data` object to be plotted
properties:
Properties of the channel
"""
if properties is None:
properties = PlotProperties()
aspect_dto = self.__build_aspect_dto(properties, data.data_type)
is_y_log = properties.is_y_log
hide_in_legend = properties.hide_in_legend
is_raw = properties.is_raw
self.__dto = TimeSeriesReferenceChannelDto(type=ChannelType.TimeSeriesReference, xyVectorId=data.vector_id, isRaw=is_raw, aspect=aspect_dto,
name=data.name, isYLog=is_y_log, wellId=well_id, hideInLegend=hide_in_legend)
def build_embedded(self, well_id: str, data: Union[Vector, Tuple[List[float], List[float]]], data_type: Optional[Union[PlotDataTypesEnum, str]] = None,
properties: Optional[PlotProperties] = None) -> None:
""" Build plot channel from embedded data
Parameters
----------
well_id:
Id of the well
data:
Data to build the channel could be either a Vector or a Tuple of lists
data_type:
Data type of the data
properties:
Properties of the channel
"""
if properties is None:
properties = PlotProperties()
if properties.y_measure is None:
y_measure = MeasureEnum.no_unit
else:
y_measure = properties.y_measure
x_values: Union[DoubleValuesDto, TimeValuesDto]
y_values: DoubleValuesDto
if type(data) is tuple:
if len(data[0]) != len(data[1]):
raise ValueError("X inputs and Y inputs have different lengths")
if properties.x_measure is None:
x_measure = MeasureEnum.no_unit
else:
x_measure = properties.x_measure
try:
x_values = DoubleValuesDto.model_validate({"type": "Double", "values": data[0], "dimension": str(x_measure.value)})
except ValidationError:
raise ValueError("X inputs are not of type Double. If you are trying to use datetime objects as X values you must create and use a Vector object as data input instead")
y_values = DoubleValuesDto.model_validate({"type": "Double", "values": data[1], "dimension": str(y_measure.value)})
if isinstance(data, Vector):
if properties.use_elapsed:
x_values = DoubleValuesDto.model_validate({"type": "Double", "values": data.elapsed_times, "dimension": "Time"})
else:
x_values = TimeValuesDto.model_validate({"type": "Time", "values": [datetime_to_str(dt) for dt in data.dates]})
y_values = DoubleValuesDto.model_validate({"type": "Double", "values": data.values, "dimension": str(y_measure.value)})
aspect_dto = self.__build_aspect_dto(properties, data_type)
name = properties.channel_name
if name is None:
name = "Channel #1"
is_y_log = properties.is_y_log
is_by_step = properties.is_by_step
first_x = properties.first_x
hide_in_legend = properties.hide_in_legend
self.__dto = EmbeddedChannelDto(type=ChannelType.Embedded, aspect=aspect_dto, name=name, isYLog=is_y_log, xValues=x_values, yValues=y_values,
isByStep=is_by_step, firstX=first_x, hideInLegend=hide_in_legend, wellId=well_id)
def build_cross_plot_channel(self, well_id: str, x_vector_id: str, y_vector_id: str, data_type: Optional[Union[PlotDataTypesEnum, str]] = None,
properties: Optional[PlotProperties] = None) -> None:
""" Build plot channel from embedded data
Parameters
----------
well_id:
Id of the well
data:
Data to build the channel could be either a Vector or a Tuple of lists
data_type:
Data type of the data
properties:
Properties of the channel
"""
if properties is None:
properties = PlotProperties()
name = properties.channel_name
if name is None:
name = "Channel #1"
aspect_dto = self.__build_aspect_dto(properties, data_type)
self.__dto = CrossReferenceChannelDto(type=ChannelType.CrossReference, aspect=aspect_dto, name=name, isYLog=properties.is_y_log, xVectorId=x_vector_id, yVectorId=y_vector_id,
hideInLegend=properties.hide_in_legend, wellId=well_id)
def __build_aspect_dto(self, properties: PlotProperties, data_type: Optional[Union[str, PlotDataTypesEnum]]) -> ChannelAspectDto:
aspect_dto = self.__get_default_aspect(data_type)
is_by_step = properties.is_by_step
show_symbols = properties.show_symbols
if data_type == PlotDataTypesEnum.shutin or data_type == PlotDataTypesEnum.shutin.value:
aspect_dto.drawingStyle = DrawingStyle.as_vertical_line
show_symbols = False
if aspect_dto.band is not None:
aspect_dto.band.opacity = 80
if (is_by_step and show_symbols is None) or show_symbols is False:
aspect_dto.symbol = None
if not properties.show_lines:
aspect_dto.line = None
return aspect_dto.__deepcopy__()
def __get_default_aspect(self, data_type: Optional[Union[str, PlotDataTypesEnum]]) -> ChannelAspectDto:
if data_type is None:
data_type = PlotDataTypesEnum.oil_rate_surface.value
if type(data_type) is PlotDataTypesEnum:
data_type = data_type.value
for data_types in self.__data_types_catalog:
if data_types.alias == data_type or data_types.name == data_type:
channnel_aspect_dto = data_types.channel_aspect_dto
if channnel_aspect_dto is None:
raise ValueError("Missing Aspect dto for channel {}".format(data_types.name))
return channnel_aspect_dto.__deepcopy__()
else:
raise ValueError("This data type does not exist")
def set_lines_aspect(self, color: Optional[str] = None, width: Optional[int] = None, style: Optional[LineAspectEnum] = None) -> None:
"""
Set up the line's aspect of the :class:`ChannelsModel`
Parameters
----------
color:
Line color
width:
Line width
style:
Line style
"""
if self.__dto is not None and self.__dto.aspect.line is not None:
if color is not None:
self.__dto.aspect.line.color = color
if width is not None:
self.__dto.aspect.line.width = width
if style is not None:
self.__dto.aspect.line.style = style
if self.__dto.aspect.line is None:
raise KeyError("Show line is set up at False, you cannot change lines properties")
else:
raise KeyError("The dto is empty, call the build_embedded or build_time_series method before changing properties")
def set_band_aspect(self, color: Optional[str] = None, opacity: Optional[int] = None) -> None:
"""
Set up the band's aspect of the :class:`ChannelsModel`
Parameters
----------
color:
Band color
opacity:
Band opacity between 0 and 100
"""
if self.__dto is not None and self.__dto.aspect.band is not None:
if color is not None:
self.__dto.aspect.band.color = color
if opacity is not None:
self.__dto.aspect.band.opacity = opacity
else:
raise KeyError("The dto is empty, call the build_embedded or the build_time_series method before changing properties")
def set_symbols_aspect(self, color: Optional[str] = None, size: Optional[int] = None, filled: Optional[bool] = None,
symbol_type: Optional[SymbolAspectEnum] = None) -> None:
"""
Set up the symbols' aspect of the :class:`ChannelsModel`
Parameters
----------
color:
Symbols' color
size:
Symbols' size
filled:
Symbols' filled
symbol_type:
Symbol's type
"""
if self.__dto is not None and self.__dto.aspect.symbol is not None:
if color is not None:
self.__dto.aspect.symbol.color = color
if size is not None:
self.__dto.aspect.symbol.size = size
if filled is not None:
self.__dto.aspect.symbol.filled = filled
if symbol_type is not None:
self.__dto.aspect.symbol.type = symbol_type
if self.__dto.aspect.symbol is None:
raise KeyError("Show symbols is set up at False, you cannot change symbols properties")
else:
raise KeyError("The dto is empty, call the build_dto method before changing properties")
def set_channel_aspect(self, drawing_style: Optional[DrawingStyle] = None, is_y_log: Optional[bool] = None, hide_in_legend: Optional[bool] = None,
is_raw: Optional[bool] = None) -> None:
"""
Set up some aspect of the :class:`ChannelsModel`
Parameters
----------
drawing_style
is_y_log
hide_in_legend
is_raw
"""
if self.__dto is not None:
if drawing_style is not None:
self.__dto.aspect.drawingStyle = drawing_style
if is_y_log is not None:
self.__dto.isYLog = is_y_log
if hide_in_legend is not None:
self.__dto.hideInLegend = hide_in_legend
if is_raw is not None:
if type(self.__dto) is not TimeSeriesReferenceChannelDto:
raise ValueError("The dto is not a time series")
else:
self.__dto.isRaw = is_raw
else:
raise KeyError("The dto is empty, call the build_dto method before changing properties")
@property
def pane_name(self) -> Optional[str]:
"""
Get associated pane name of the :class: PlotChannel
"""
return self.__pane_name
@property
def dto(self) -> Optional[Union[EmbeddedChannelDto, TimeSeriesReferenceChannelDto, CrossReferenceChannelDto]]:
"""
Get associated dto of the :class: PlotChannel
"""
return self.__dto