Source code for ladybug_display.extension.psychchart

"""Method to draw an PsychrometricChart as a VisualizationSet."""
from ladybug_geometry.geometry3d import Vector3D, Point3D, Plane, LineSegment3D, \
    Polyline3D, Mesh3D
from ladybug.datatype.time import Time
from ladybug.datacollection import BaseCollection
from ladybug.legend import LegendParameters

from ladybug_display.geometry3d import DisplayLineSegment3D, DisplayPolyline3D, \
    DisplayText3D
from ladybug_display.visualization import VisualizationSet, AnalysisGeometry, \
    VisualizationData, ContextGeometry


[docs] def psychrometric_chart_to_vis_set( psych_chart, data=None, legend_parameters=None, z=0, plot_wet_bulb=False): """Get a Ladybug PsychrometricChart represented as a VisualizationSet. Args: psych_chart: A Ladybug PsychrometricChart object. data: An optional list of data collection objects, which are aligned with the psychrometric chart temperature and relative_humidity and will generate additional colored AnalysisGeometries on the chart. legend_parameters: An optional LegendParameter object or list of LegendParameter objects to customize the display of the data on the psychrometric chart. Note that this relates only to the data supplied as input for this method and, to customize the display of the time/frequency mesh, the PsychrometricChart's native legend_parameters should be edited. If a list is used here, this should align with the input data (one legend parameter per data collection). z: A number for the Z-coordinate to be used in translation. (Default: 0). plot_wet_bulb: Boolean to note whether the psychrometric chart should be plotted with lines of constant enthalpy (False) or lines of constant wet bulb temperature (True). (Default: False). Returns: A VisualizationSet with the psychrometric chart represented several ContextGeometries and an AnalysisGeometry. This includes these objects in the following order. - Title -- A ContextGeometry for the title and border around the psychrometric chart. - Temperature_Axis -- A ContextGeometry with lines and text for the Temperature (X) axis of the psychrometric chart. - Humidity_Axis -- A ContextGeometry with lines and text for the Humidity (Y) axis of the psychrometric chart. - Relative_Humidity_Lines -- A ContextGeometry with lines and text for the relative humidity of the psychrometric chart. - Enthalpy_Lines -- A ContextGeometry with lines and text for the enthalpy of the psychrometric chart. This layer will not be included if plot_wet_bulb is True. - Wet_Bulb_Lines -- A ContextGeometry with lines and text for the wet bulb temperature of the psychrometric chart. This layer will not be included if plot_wet_bulb is False. - Analysis_Data -- An AnalysisGeometry for the data on the psychrometric chart. This will include multiple data sets if the data input is provided. """ # establish the VisualizationSet object vis_set = VisualizationSet('Psychrometric_Chart', ()) vis_set.display_name = 'Psychrometric Chart' # get values used throughout the translation txt_hgt = psych_chart.legend_parameters.text_height font = psych_chart.legend_parameters.font bp = Plane(o=Point3D(0, 0, z)) # add the title and border if isinstance(psych_chart.temperature, BaseCollection): meta_i = psych_chart.temperature.header.metadata.items() title_items = ['Time [hr]'] + ['{}: {}'.format(k, v) for k, v in meta_i] else: title_items = ['Psychrometric Chart'] ttl_pl = psych_chart.container.upper_title_location if z != 0: ttl_pl = Plane(n=ttl_pl.n, o=Point3D(ttl_pl.o.x, ttl_pl.o.y, z), x=ttl_pl.x) ttl_txt = DisplayText3D( '\n'.join(title_items), ttl_pl, txt_hgt * 1.5, None, font, 'Left', 'Top') border_geo = Polyline3D.from_polyline2d(psych_chart.chart_border, bp) sat_geo = Polyline3D.from_polyline2d(psych_chart.saturation_line, bp) title_objs = [ttl_txt, DisplayPolyline3D(sat_geo, line_width=2), DisplayPolyline3D(border_geo, line_width=2)] title = ContextGeometry('Title', title_objs) vis_set.add_geometry(title) # add the temperature axis tm_pl = _plane_from_point(psych_chart.x_axis_location, z) temp_txt = DisplayText3D( psych_chart.x_axis_text, tm_pl, txt_hgt * 1.5, None, font, 'Left', 'Top') temp_geo = [temp_txt] for tl in psych_chart.temperature_lines: tl_geo = LineSegment3D.from_line_segment2d(tl, z) temp_geo.append(DisplayLineSegment3D(tl_geo)) tl_pts = psych_chart.temperature_label_points for txt, pt in zip(psych_chart.temperature_labels, tl_pts): t_pln = Plane(o=Point3D(pt.x, pt.y, z)) txt_obj = DisplayText3D(txt, t_pln, txt_hgt, None, font, 'Center', 'Top') temp_geo.append(txt_obj) temp_axis = ContextGeometry('Temperature_Axis', temp_geo) temp_axis.display_name = 'Temperature Axis' vis_set.add_geometry(temp_axis) # add the humidity axis hr_pl = _plane_from_point(psych_chart.y_axis_location, z, Vector3D(0, 1)) hr_txt = DisplayText3D( psych_chart.y_axis_text, hr_pl, txt_hgt * 1.5, None, font, 'Right', 'Top') hr_geo = [hr_txt] for hl in psych_chart.hr_lines: hl_geo = LineSegment3D.from_line_segment2d(hl, z) hr_geo.append(DisplayLineSegment3D(hl_geo)) for txt, pt in zip(psych_chart.hr_labels, psych_chart.hr_label_points): t_pln = Plane(o=Point3D(pt.x, pt.y, z)) txt_obj = DisplayText3D(txt, t_pln, txt_hgt, None, font, 'Left', 'Middle') hr_geo.append(txt_obj) hr_axis = ContextGeometry('Humidity_Axis', hr_geo) hr_axis.display_name = 'Humidity Axis' vis_set.add_geometry(hr_axis) # add the relative humidity lines rh_geo = [] for rl in psych_chart.rh_lines: rl_geo = Polyline3D.from_polyline2d(rl, bp) rh_geo.append(DisplayPolyline3D(rl_geo)) for txt, pt in zip(psych_chart.rh_labels[:-1], psych_chart.rh_label_points[:-1]): t_pln = Plane(o=Point3D(pt.x, pt.y, z)) txt_obj = DisplayText3D(txt, t_pln, txt_hgt * 0.8, None, font, 'Right', 'Middle') rh_geo.append(txt_obj) rh_axis = ContextGeometry('Relative_Humidity_Lines', rh_geo) rh_axis.display_name = 'Relative Humidity Lines' vis_set.add_geometry(rh_axis) # add enthalpy or wet bulb lines if plot_wet_bulb: wb_geo = [] for wl in psych_chart.wb_lines: wl_geo = LineSegment3D.from_line_segment2d(wl, z) wb_geo.append(DisplayLineSegment3D(wl_geo, line_type='Dotted')) for txt, pt in zip(psych_chart.wb_labels, psych_chart.wb_label_points): t_pln = Plane(o=Point3D(pt.x, pt.y, z)) txt_obj = DisplayText3D(txt, t_pln, txt_hgt, None, font, 'Right', 'Middle') wb_geo.append(txt_obj) wb_axis = ContextGeometry('Wet_Bulb_Lines', wb_geo) wb_axis.display_name = 'Wet Bulb Lines' vis_set.add_geometry(wb_axis) else: enth_geo = [] for wl in psych_chart.enthalpy_lines: wl_geo = LineSegment3D.from_line_segment2d(wl, z) enth_geo.append(DisplayLineSegment3D(wl_geo, line_type='Dotted')) enth_pts = psych_chart.enthalpy_label_points for txt, pt in zip(psych_chart.enthalpy_labels, enth_pts): t_pln = Plane(o=Point3D(pt.x, pt.y, z)) txt_obj = DisplayText3D(txt, t_pln, txt_hgt, None, font, 'Right', 'Middle') enth_geo.append(txt_obj) enth_axis = ContextGeometry('Enthalpy_Lines', enth_geo) enth_axis.display_name = 'Enthalpy Lines' vis_set.add_geometry(enth_axis) # add the analysis geometry # ensure 3D legend defaults are overridden to make the data readable l_par = psych_chart.legend.legend_parameters.duplicate() l_par.base_plane = l_par.base_plane l_par.segment_height = l_par.segment_height l_par.segment_width = l_par.segment_width # gather all of the visualization data sets vis_data = [VisualizationData(psych_chart.hour_values, l_par, Time(), 'hr')] if data is not None and len(data) != 0: if legend_parameters is None: l_pars = [LegendParameters()] * len(data) elif isinstance(legend_parameters, LegendParameters): l_pars = [legend_parameters] * len(data) else: # assume it's a list that aligns with the data l_pars = legend_parameters for dat, lp in zip(data, l_pars): # process the legend parameters lp = lp.duplicate() if lp.is_base_plane_default: lp.base_plane = l_par.base_plane if lp.is_segment_height_default: lp.segment_height = l_par.segment_height if lp.is_segment_width_default: lp.segment_width = l_par.segment_width # check to be sure the data collection aligns d_vals = dat.values assert len(d_vals) == psych_chart._calc_length, \ 'Number of data collection values ' \ 'must match those of the psychrometric chart temperature and humidity.' # create a matrix with a tally of the hours for all the data base_mtx = [[[] for val in psych_chart._t_category] for rh in psych_chart._rh_category] for t, rh, v in zip(psych_chart._t_values, psych_chart._rh_values, d_vals): if t < psych_chart._min_temperature or t > psych_chart._max_temperature: continue # temperature value does not currently fit on the chart for y, rh_cat in enumerate(psych_chart._rh_category): if rh < rh_cat: break for x, t_cat in enumerate(psych_chart._t_category): if t < t_cat: break base_mtx[y][x].append(v) # compute average values avg_values = [sum(val_list) / len(val_list) for rh_l in base_mtx for val_list in rh_l if len(val_list) != 0] hd = dat.header vd = VisualizationData(avg_values, lp, hd.data_type, hd.unit) vis_data.append(vd) # create the analysis geometry mesh_3d = Mesh3D.from_mesh2d(psych_chart.colored_mesh, bp) mesh_geo = AnalysisGeometry( 'Analysis_Data', [mesh_3d], vis_data, active_data=len(vis_data) - 1) mesh_geo.display_name = 'Analysis Data' mesh_geo.display_mode = 'Surface' vis_set.add_geometry(mesh_geo) return vis_set
def _plane_from_point(point_2d, z, align_vec=Vector3D(1, 0, 0)): """Get a Plane from a Point2D. Args: point_2d: A Point2D to serve as the origin of the plane. z: The Z value for the plane origin. align_vec: A Vector3D to serve as the X-Axis of the plane. """ return Plane(o=Point3D(point_2d.x, point_2d.y, z), x=align_vec)