def _marker( locs_coordinates, location, carrier, tech, prod, scale_factor, techs_colors, is_initial_timestep, add_legend, name, ): # Example: "Region1: 3552.65 of pipe_import (gas)" hover_info = "%s: %.2f of %s (%s)" % (location, prod, tech, carrier) marker_dict = dict( visible=False, hoverinfo="text", text=hover_info, mode="markers", marker=dict( symbol="circle-dot", opacity=0.6, size=prod * scale_factor + 1, color=techs_colors[tech], ), legendgroup=tech, showlegend=False, ) marker_legend = dict( visible=False, hoverinfo="text", text=hover_info, mode="markers", marker=dict( symbol="circle-dot", opacity=0.6, size=10, color=techs_colors[tech], ), legendgroup=tech, name=break_name(name, 18), ) if set(locs_coordinates.index) == set(["x", "y"]): h_coord, v_coord = "x", "y" elif set(locs_coordinates.index) == set(["lon", "lat"]): h_coord, v_coord = "lon", "lat" marker_dict[h_coord] = [locs_coordinates[location][h_coord]] marker_dict[v_coord] = [locs_coordinates[location][v_coord]] marker_legend[h_coord] = [None] marker_legend[v_coord] = [None] if is_initial_timestep: # plot only the first timestep data when the chart is initialized marker_dict["visible"] = True marker_legend["visible"] = True if add_legend: return [marker_dict, marker_legend] else: return [marker_dict]
def _line( locs_coordinates, transmission_type, to_location, from_location, carrier, tech, prod, scale_factor, techs_colors, is_initial_timestep, add_legend, name, ): # e.g. "Region1->Region2: 256.54 by gas_transmission (gas)" hover_info = "%s->%s: %.2f by %s (%s)" % ( from_location, to_location, prod, transmission_type, carrier, ) line = dict( visible=False, mode="lines", hoverinfo="text", text="", line=dict(width=prod * scale_factor + 1, color=techs_colors[transmission_type]), legendgroup=transmission_type, opacity=0.6, showlegend=False, ) line_legend = dict( visible=False, mode="lines", hoverinfo="text", text="", line=dict(width=10, color=techs_colors[transmission_type]), legendgroup=transmission_type, name=break_name(name, 18), opacity=0.6, ) line_info_marker = dict( visible=False, mode="markers", hoverinfo="text", text=hover_info, marker=dict(symbol="square", opacity=0, color=techs_colors[transmission_type]), legendgroup=transmission_type, name=tech, showlegend=False, ) if set(locs_coordinates.index) == set(["x", "y"]): h_coord, v_coord = "x", "y" elif set(locs_coordinates.index) == set(["lon", "lat"]): h_coord, v_coord = "lon", "lat" line[h_coord] = [ locs_coordinates[from_location][h_coord], locs_coordinates[to_location][h_coord], ] line[v_coord] = [ locs_coordinates[from_location][v_coord], locs_coordinates[to_location][v_coord], ] line_legend[h_coord] = [None] line_legend[v_coord] = [None] line_info_marker[h_coord] = [ (1 / 2) * ( locs_coordinates[from_location][h_coord] + locs_coordinates[to_location][h_coord] ) ] line_info_marker[v_coord] = [ (1 / 2) * ( locs_coordinates[from_location][v_coord] + locs_coordinates[to_location][v_coord] ) ] if is_initial_timestep: # plot only the first timestep data when the chart is initialized line["visible"] = True line_legend["visible"] = True line_info_marker["visible"] = True if add_legend: return [line, line_legend, line_info_marker] else: return [line, line_info_marker]
def _get_var_data(var, model, dataset, visible, subset, sum_dims, squeeze): """ Get variable data from model_data and use it to populate a list with Plotly plots """ carriers = list(dataset.carriers.values) # list to populate data = [] # Clustered data does not get plotted on a datetime axis if "clusters" in dataset.dims: clusters = dataset.timestep_cluster.to_pandas() cluster_groups = clusters.groupby(clusters).groups # Add an extra timestep at the end of each cluster, so we can break # scatter lines with a NaN between each cluster dummy_timestep = lambda x: pd.to_datetime(x[-1] + pd.Timedelta(100, unit="ns")) reindexing_timesteps = np.concatenate( [ group_timesteps.append( pd.Index([dummy_timestep(group_timesteps)]) ).values for group_timesteps in cluster_groups.values() ] ) timesteps = pd.Index(reindexing_timesteps).values.astype(str) # replace dummy timestep with NaN timesteps[pd.Index(reindexing_timesteps).nanosecond == 100] = np.nan timesteps = pd.to_datetime(timesteps, errors="ignore") if var == "storage_inter_cluster": datesteps = pd.to_datetime(model._model_data.datesteps.values) else: timesteps = pd.to_datetime(model._model_data.timesteps.values) def _get_reindexed_array(array, index=None, fillna=None): if index is None: index = ["nodes", "techs"] # reindexing data means that DataArrays have the same values in nodes and techs reindexer = {k: sorted(dataset[k].values) for k in index} formatted_array = model.get_formatted_array(array) if fillna is not None: return formatted_array.reindex(**reindexer).fillna(fillna) else: return formatted_array.reindex(**reindexer) def _get_y_vals(array, ignore_clustering=False): if "clusters" in dataset.dims and not ignore_clustering: return array.to_pandas().reindex(reindexing_timesteps).values else: return array.values if hasattr(model, "results"): array_prod = _get_reindexed_array( "carrier_prod", index=["nodes", "techs", "carriers"], fillna=0 ) array_con = _get_reindexed_array( "carrier_con", index=["nodes", "techs", "carriers"], fillna=0 ) if "resource_con" in dataset.data_vars: resource_con = _get_reindexed_array("resource_con", fillna=0) else: resource_con = 0 # carrier flow is a combination of carrier_prod, carrier_con and # carrier_export for a given energy carrier if var in carriers: array_flow = ( array_prod.loc[dict(carriers=var)] + array_con.loc[dict(carriers=var)] ) if "carrier_export" in dataset: export_flow = subset_sum_squeeze( _get_reindexed_array( "carrier_export", index=["nodes", "techs", "carriers"], fillna=0 ).loc[dict(carriers=var)], subset, sum_dims, squeeze, ) if "unmet_demand" in dataset: unmet_flow = subset_sum_squeeze( _get_reindexed_array( "unmet_demand", index=["nodes", "carriers"], fillna=0 ).loc[dict(carriers=var)], subset, sum_dims, squeeze=False, ) # array flow for storage tracks stored energy. carrier_flow is # charge/discharge (including resource consumed for supply_plus techs) elif var == "storage": array_flow = _get_reindexed_array("storage") if "resource_eff" in dataset.data_vars: resource_eff = _get_reindexed_array("resource_eff", fillna=1) else: resource_eff = 1 charge = subset_sum_squeeze( -array_con.sum("carriers") + resource_con * resource_eff, subset, sum_dims, squeeze=False, ) discharge = -subset_sum_squeeze( array_prod.sum("carriers"), subset, sum_dims, squeeze=False ) elif var == "resource_con": array_flow = resource_con else: array_flow = _get_reindexed_array(var) array_flow = subset_sum_squeeze(array_flow, subset, sum_dims, squeeze) err_details = " (variable: `{}`, subset: `{}`, sum_dims: `{}`.".format( var, subset, sum_dims ) if "timesteps" not in array_flow.dims and "datesteps" not in array_flow.dims: raise ValueError( "`timesteps` not in plotting data, cannot proceed" + err_details ) if len(array_flow.dims) > 2: if "costs" in array_flow.dims: err_details = err_details + ( " Try subsetting to select a cost class, " "e.g. subset={'costs': ['monetary']}." ) raise ValueError( "Too many dimensions to plot: `{}`".format(array_flow.dims) + err_details ) # If techs not given as a subset (implying a desire to order manually), # sort techs such that those varying output least are at the bottom of the stack if subset.get("techs", None) or var == "storage_inter_cluster": sorted_techs = array_flow.techs.values else: sorted_techs = [ array_flow.techs.values[i] for i in array_flow.var(dim="timesteps").argsort() ] # for tech in array_flow.techs.values: for tech in sorted_techs: tech_dict = {"techs": tech} if not array_flow.loc[tech_dict].sum(): continue # We allow transmission tech information to show up in some cases if ( "techs_transmission" in dataset and tech in dataset.techs_transmission.values ): base_tech = "transmission" color = dataset.colors.loc[{"techs": tech.split(":")[0]}].item() name = break_name( dataset.names.loc[{"techs": tech.split(":")[0]}].item(), 30 ) if var in carriers: continue # no transmission in carrier flow else: base_tech = dataset.inheritance.loc[tech_dict].item().split(".")[0] color = dataset.colors.loc[tech_dict].item() name = break_name(dataset.names.loc[tech_dict].item(), 30) if base_tech == "demand": # Always insert demand at position 0 in the list, to make # sure it appears on top in the legend data.insert( 0, go.Scatter( x=timesteps, y=_get_y_vals(-array_flow.loc[tech_dict]), visible=visible, line=dict(color=color), name=name, legendgroup=tech, ), ) elif var == "storage": # stored energy as scatter, carrier/resource prod/con as stacked bar data.insert( 0, go.Scatter( x=timesteps, y=_get_y_vals(array_flow.loc[tech_dict]), visible=visible, line=dict(color=color), mode="lines", name=name + " stored energy", showlegend=False, text=tech + " stored energy", hoverinfo="x+y+text", legendgroup=tech, ), ) data.append( go.Bar( x=timesteps, y=_get_y_vals(charge.loc[tech_dict]), visible=visible, name=name, marker=dict(color=color), legendgroup=tech, text=tech + " charge", hoverinfo="x+y+text", ) ) data.append( go.Bar( x=timesteps, y=_get_y_vals(discharge.loc[tech_dict]), visible=visible, name=name, marker=dict(color=color, opacity=0.8), legendgroup=tech, text=tech + " discharge", hoverinfo="x+y+text", showlegend=False, ) ) elif var == "storage_inter_cluster": data.append( go.Scatter( x=datesteps, y=_get_y_vals(array_flow.loc[tech_dict], ignore_clustering=True), visible=visible, line=dict(color=color), mode="lines", name=name, showlegend=False, text=tech, hoverinfo="x+y+text", legendgroup=tech, ) ) else: data.append( go.Bar( x=timesteps, y=_get_y_vals(array_flow.loc[tech_dict]), visible=visible, name=name, legendgroup=tech, marker=dict(color=color), ) ) if ( var in carriers and "carrier_export" in dataset and export_flow.loc[tech_dict].sum() ): data.append( go.Bar( x=timesteps, y=_get_y_vals(-export_flow.loc[tech_dict]), visible=visible, name=name + " export", legendgroup=tech, marker=dict(color=hex_to_rgba(color, 0.5)), ) ) if var in carriers and "unmet_demand" in dataset: data.append( go.Bar( x=timesteps, y=_get_y_vals(unmet_flow), visible=visible, name="Unmet " + var + " demand", legendgroup="unmet_demand", marker=dict(color="grey"), ) ) return data
def plot_transmission(model, mapbox_access_token=None, **kwargs): """ Parameters ---------- mapbox_access_token : str, optional If given and a valid Mapbox API key, a Mapbox map is drawn for lat-lon coordinates, else (by default), a more simple built-in map. """ try: coordinates = model._model_data.loc_coordinates.sortby("locs") except AttributeError: raise ValueError( "Model does not define location coordinates " "- no transmission plotting possible." ) colors = model._model_data.colors names = model._model_data.names plot_width = 1000 if hasattr(model, "results"): energy_cap = _get_data(model, "energy_cap") carrier_prod = _get_data( model, "carrier_prod", sum_dims=["timesteps", "carriers"] ) carrier_con = _get_data( model, "carrier_con", sum_dims=["timesteps", "carriers"] ) energy_flow = carrier_con.fillna(0) + carrier_prod.fillna(0) else: energy_cap = _get_data(model, "energy_cap_max") energy_flow = energy_cap.copy() energy_flow.loc[dict()] = 0 if sorted(coordinates.coordinates.values) == ["lat", "lon"]: h_coord, v_coord = ("lat", "lon") if mapbox_access_token: scatter_type = "scattermapbox" layout_dict = dict( mapbox=dict( accesstoken=mapbox_access_token, center=_get_centre(coordinates), zoom=_get_zoom(coordinates, plot_width), style="light", ) ) else: scatter_type = "scattergeo" layout_dict = dict( geo=dict( scope="world", projection=dict(type="mercator", scale=1), showland=True, showcountries=True, showsubunits=True, showocean=True, showrivers=True, showlakes=True, lonaxis=dict(range=get_range(coordinates, "lon", 0.1)), lataxis=dict(range=get_range(coordinates, "lat", 0.1)), resolution=50, landcolor="rgba(240, 240, 240, 0.8)", oceancolor="#aec6cf", subunitcolor="blue", countrycolor="green", countrywidth=0.5, subunitwidth=0.5, ) ) else: h_coord, v_coord = ("x", "y") scatter_type = "scatter" layout_dict = dict() mid_edge_scatter_dict = { "type": scatter_type, "showlegend": False, "mode": "markers", "hoverinfo": "text", "opacity": 0, } edge_scatter_dict = { "type": scatter_type, "mode": "lines", "hoverinfo": "none", "opacity": 0.8, } data = [] for tech in sorted(energy_cap.techs.values): per_tech_mid_edge_dict = mid_edge_scatter_dict.copy() per_tech_mid_edge_dict = { **mid_edge_scatter_dict, **{ h_coord: _fill_scatter( coordinates, energy_cap, "mid_edge", h_coord, tech ), v_coord: _fill_scatter( coordinates, energy_cap, "mid_edge", v_coord, tech ), "text": _fill_scatter( coordinates, energy_cap, "mid_edge", "text", tech ), "legendgroup": tech, "marker": {"color": colors.loc[dict(techs=tech)].item()}, }, } h_edge = _fill_scatter(coordinates, energy_cap, "edge", h_coord, tech) v_edge = _fill_scatter(coordinates, energy_cap, "edge", v_coord, tech) showlegend = True for i in range(len(h_edge)): data.append( { **edge_scatter_dict, **{ h_coord: h_edge[i], v_coord: v_edge[i], "showlegend": showlegend, "legendgroup": tech, "name": break_name(names.loc[dict(techs=tech)].item(), 30), "line": {"color": colors.loc[dict(techs=tech)].item()}, }, } ) showlegend = False data.append(per_tech_mid_edge_dict) node_scatter_dict = { h_coord: [ coordinates.loc[dict(locs=loc, coordinates=h_coord)].item() for loc in coordinates.locs ], v_coord: [ coordinates.loc[dict(locs=loc, coordinates=v_coord)].item() for loc in coordinates.locs ], "text": [loc.item() for loc in coordinates.locs], "name": "Locations", "type": scatter_type, "legendgroup": "locations", "mode": "markers", "hoverinfo": "text", "marker": {"symbol": "square", "size": 8, "color": "grey"}, } data.append(node_scatter_dict) layout_dict.update( dict( width=plot_width, title=model.model_config["name"], autosize=True, hovermode="closest", showlegend=True, ) ) if kwargs.get("html_only", False): del layout_dict["title"] del layout_dict["width"] return data, layout_dict
def _get_var_data( cap, model, dataset, visible, subset, sum_dims, squeeze, locations, orientation ): if "systemwide" in cap: array_cap = subset_sum_squeeze(dataset[cap], subset) if "costs" in array_cap.dims and len(array_cap["costs"]) == 1: array_cap = array_cap.squeeze("costs") elif "costs" in array_cap.dims and len(array_cap["costs"]) > 1: raise ValueError( "Cannot plot {} without subsetting to pick one cost type " "of interest".format(cap) ) if "carriers" not in subset.keys(): array_cap = array_cap.sortby("carriers") else: array_cap = model.get_formatted_array(cap).reindex(locs=locations) array_cap = subset_sum_squeeze(array_cap, subset, sum_dims, squeeze) if len(array_cap.dims) > 2: raise ValueError( "Maximum two dimensions allowed for plotting capacity, but {} " "given as dimensions for {}".format(array_cap.dims, cap) ) if "techs" not in array_cap.dims: raise ValueError("Cannot plot capacity without `techs` in dimensions") elif "techs" not in subset.keys(): array_cap = array_cap.sortby("techs") data = [] for tech in array_cap.techs.values: if tech not in dataset.techs.values: continue if ( "techs_transmission" in dataset and tech in dataset.techs_transmission.values ): continue else: base_tech = dataset.inheritance.loc[{"techs": tech}].item().split(".")[0] if base_tech in "demand": continue if array_cap.loc[{"techs": tech}].sum() > 0: x = array_cap.loc[{"techs": tech}].values name = break_name(model._model_data.names.loc[{"techs": tech}].item(), 30) if "systemwide" in cap: y = natsorted(array_cap.carriers.values) else: if "locs" in array_cap.dims: y = natsorted(array_cap.locs.values) else: # Single location y = [array_cap.locs.values] if orientation == "v": x, y = y, x # Flip axes hoverinfo = "y+name" else: hoverinfo = "x+name" x, y = x[::-1], y[::-1] # Make sure that sorting is from bottom down data.append( go.Bar( x=x, y=y, visible=visible, name=name, legendgroup=tech, text=tech, hoverinfo=hoverinfo, marker=dict( color=model._model_data.colors.loc[{"techs": tech}].item() ), orientation=orientation, ) ) return data