def add_chart_title(fig: go.Figure, title: str, elevate_line: int = 1,
    title_margin: int = 40, font_size: int = 12, **kwargs) -> None:

    y_line = 0.98 + (elevate_line * 0.04)
    y_annotation = y_line

    fig.add_annotation(
        text=title,
        font={'size': font_size},
        align='left',
        yanchor='bottom',
        xref='paper', x=0.5, yref='paper', y=y_annotation,
        showarrow=False,
        **kwargs
    )

    fig.add_shape(
        type='line',
        xref='paper', x0=0, x1=1,
        yref='paper', y0=y_line, y1=y_line,
        line=dict(
            width=1
        )
    )

    if title_margin:
        fig.update_layout(
            dict(
                margin={'t': title_margin + (elevate_line * 30)},
            )
        )
Exemplo n.º 2
0
def format_well_overview_figure(figure: go.Figure, charttype: str,
                                settings: List[str], sumvec: str) -> go.Figure:
    """This function formate the well overview figure. The reason for keeping this
    function outside the figure class is that we can update the figure formatting
    without generating a new WellOverviewFigure object and reloading the data. It can
    be applied directly to the current state of the figure dict if only formatting
    settings are changed. See in the well_overview_callbacks how it is used.
    """

    if charttype == "pie":
        figure.update_traces(
            texttemplate=("%{label}<br>%{value:.2s}" if "show_prod_text" in
                          settings else "%{label}"))

    elif charttype == "bar":
        figure.update_layout(
            barmode=("overlay" if "overlay_bars" in settings else "group"))
        figure.update_traces(
            textposition=("auto" if "show_prod_text" in settings else "none"))

    # These are valid for all chart types
    figure.update_layout(template=(
        "plotly_white" if "white_background" in settings else "plotly"))

    phase = {"WOPT": "Oil", "WGPT": "Gas", "WWPT": "Water"}[sumvec]

    figure.update(
        layout_title_text=f"Cumulative Well {phase} Production (Sm3)",
        layout_showlegend=("legend" in settings),
    )
    return figure
    def init_figure(self, analyzed_interval, predictions, model):
        """
        Initializes the plotly Figure and puts predictions argument 
        as candlestick data into the chart
        Parameters:
        analyzed_interval : np.ndarray - dates and prices 
        predictions : np.ndarray - hardmax indices of model's predictions 
        model : keras.Model
        Returns:
        figure : plotly.Figure 
        """
        candlestick = Candlestick(x=analyzed_interval[:, 0],
                                  open=analyzed_interval[:, 1],
                                  high=analyzed_interval[:, 2],
                                  low=analyzed_interval[:, 3],
                                  close=analyzed_interval[:, 4])

        figure = Figure(data=[candlestick])

        model.categories.sort()
        steps = ', '.join(['+' + str(c) + '%' for c in model.categories])
        title = ('Predictions of ' + model.name + ' for the next ' +
                 str(model.prediction_interval) +
                 ' days based on the previous ' + str(model.input_interval) +
                 ' days. Steps: ' + steps)
        figure.update_layout(title=title, yaxis_title='Price')

        return figure
Exemplo n.º 4
0
def update_layout(fig: go.Figure) -> None:
    fig.update_layout(
        xaxis={
            'tick0': 0,
            'dtick': 20,
            'ticks': 'outside',
            'ticklen': 5,
            'tickwidth': 2,
            'tickangle': 45,
            'tickfont': {'size': 14},
            'showline': True, 
            'linewidth': 2, 
            'linecolor': 'black',
        },
        yaxis={
            'showgrid': True,
            'gridwidth': 0.5,
            'gridcolor': GRID_COLOR,
            'ticks': 'outside',
            'ticklen': 5,
            'tickwidth': 2,
            'tickfont': {'size': 14},
            'showline': True, 
            'linewidth': 2, 
            'linecolor': 'black',
            'tick0': 0,
            'dtick': 1,
        },
        plot_bgcolor="white",
        legend={'orientation': 'h', 'x': 0, 'y': 1.2},
    )
Exemplo n.º 5
0
def update_axes(fig: Figure, temp_kwargs: Dict[str, Any]) -> None:
    """
    Separeated this portion of the code because it is clumsy. It changes
    the axes looks.
    """
    # Layout and title parameters https://plotly.com/python/figure-labels/
    fig.update_layout(scene=dict(
        xaxis=dict(
            title_text=temp_kwargs['x_label'],
            showline=True,
            linewidth=2,
            linecolor='black',
            ticks="outside",
            mirror=True,
            backgroundcolor='#9467bd',
        ),
        yaxis=dict(
            title_text=temp_kwargs['y_label'],
            showline=True,
            linewidth=2,
            linecolor='black',
            ticks="outside",
            mirror=True,
            backgroundcolor='#9467bd',
        ),
        zaxis=dict(
            title_text=temp_kwargs['z_label'],
            showline=True,
            linewidth=2,
            linecolor='black',
            ticks="outside",
            mirror=True,
            backgroundcolor='#9467bd',  #change the color of axis
        )))
Exemplo n.º 6
0
def update_layout(df: pd.DataFrame, fig: go.Figure) -> None:
    fig.update_layout(
        xaxis={
            'tickvals': df['date'],
            'tickformat': DATE_FORMAT,
            'tickangle': 45,
            'type': 'date',
            'ticks': 'outside',
            'ticklen': 5,
            'tickwidth': 2,
            'showline': True,
            'linewidth': 2,
            'linecolor': 'black',
            'tickfont': {
                'size': 14
            },
        },
        yaxis={
            'showgrid': True,
            'gridwidth': 0.5,
            'gridcolor': GRID_COLOR,
            'ticks': 'outside',
            'ticklen': 5,
            'tickwidth': 2,
            'showline': True,
            'linewidth': 2,
            'linecolor': 'black',
            #'dtick': 20,
            'tickfont': {
                'size': 14
            },
        },
        plot_bgcolor="white",
    )
 def _update_plot_layout(initial_layout: dict, update_layout: dict) -> Dict:
     if initial_layout is None:
         raise PreventUpdate
     fig = Figure({"layout": initial_layout})
     if update_layout is not None:
         fig.update_layout(update_layout)
     return fig["layout"]
Exemplo n.º 8
0
    def countries_scatters(self, countries: list, column: str, mode: str=None, showlegend: bool=True) -> Figure:
        fig = Figure()

        ratio_re = re.compile(r"(?x: \b Ratio \b )")
        daily_re = re.compile(r"(?x: \b Daily \b )")

        if ratio_re.search(column) is not None:
            fig.update_layout(yaxis=dict(tickformat="%.format.%3f"))

        for country in countries:
            country_df = self.main_df.loc[[country]].reset_index(level=0, drop=True)

            if daily_re.search(column) is not None:
                country_df = country_df[1:]

            fig.add_trace(
                Scatter(
                    x=country_df.index,
                    y=country_df[column],
                    name=country,
                    mode=mode,
                    line_color=self.color.get(country, "black"),
                    opacity=0.9,
                    showlegend=showlegend,
                )
            )

        fig.update_layout(title_text=column)

        return fig
Exemplo n.º 9
0
    def plot_3d(self):
        z = self.get_point_clouds()

        fig = Figure(data=Scatter3d(x=z[:,0].ravel(), y=z[:,1].ravel(), z=z[:,2].ravel(),
                                        mode='markers',
                                        marker=dict(size=1)        
                                        )
                        )

        x_range = [floor(z[:,0].min()), ceil(z[:,0].max())]
        y_range = [floor(z[:,1].min()), ceil(z[:,1].max())]
        z_range = [floor(z[:,2].min()), ceil(z[:,2].max())]

        x_len = x_range[1] - x_range[0]
        y_len = y_range[1] - y_range[0]
        z_len = z_range[1] - z_range[0]
        base = min([x_len, y_len, z_len])
        
        fig.update_layout(
            scene = dict(
                aspectmode='manual', aspectratio=dict(x=x_len/base, y=y_len/base, z=z_len/base),
                xaxis = dict(range=x_range),
                yaxis = dict(range=y_range),
                zaxis = dict(range=z_range)
            )
        )

        return fig
Exemplo n.º 10
0
    def countries_shifted_scatters(self, countries: list, column: str, threshold: int = 100, mode: str = None, showlegend: bool=True) -> Figure:
        countries_df = self.countries_dataframe(countries=countries, column=column, threshold=threshold)
        countries_df.dropna(axis="index", how="all", inplace=True)
        countries_df = countries_df.apply(lambda x: Series(x.dropna().values))

        engine = inflect.engine()

        fig = Figure()

        for country in countries:
            fig.add_trace(
                Scatter(
                    x=countries_df.index,
                    y=countries_df[country],
                    name=country,
                    mode=mode,
                    line_color=self.color.get(country, "black"),
                    opacity=0.9,
                    showlegend=showlegend,
                )
            )

        fig.update_layout(yaxis_type="log")
        fig.update_layout(title_text="Days since \"{}\" column above {} {}".format(column, threshold, engine.plural_noun("occurence", threshold)))

        return fig
    def show_plot(fig: go.Figure) -> None:
        """Save plot to the specified file."""

        # Show only if SHOW_PLOT is True
        if SHOW_PLOT:
            fig.update_layout(template=_plot_layout_template)
            fig.show()
Exemplo n.º 12
0
def customize_figure(fig: Figure) -> Figure:
    """Update the layout and style of plotly figures.

    Parameters
    ----------
    fig : plotly.graph_objs._figure.Figure
        The figure to modify

    Returns
    -------
    plotly.graph_objs._figure.Figure
        A customized figure.
    """
    fig.update_xaxes(fixedrange=True, title="Values", title_font_size=12)
    fig.update_yaxes(fixedrange=True, title_font_size=12)
    fig.update_layout(
        font_family="Courier New",
        paper_bgcolor="#205050",
        plot_bgcolor="#205050",
        margin={
            "l": 60,
            "t": 40,
            "r": 10,
            "b": 10
        },
        title_font_size=13,
        template="plotly_dark",
    )
    return fig
Exemplo n.º 13
0
    def whatevers_bar_chart(self, whatever: str="total") -> Figure:
        """ Stacked bar chart of total active, deaths and recovered values """

        columns = ["active", "deaths", "recovered"]
        dates   = self.main_df.reset_index()["Date"]
        color   = {
            "active":    "#ffb347",  # Pastel Orange
            "deaths":    "#ff6961",  # Pastel Red
            "recovered": "#77dd78",  # Pastel Green
        }

        engine = inflect.engine()

        fig = Figure()

        for column in columns:
            name = "{} {}".format(whatever.capitalize(), column.capitalize())

            ys = self.main_df[name].sum(level=1)
            if whatever == "daily":
                ys = ys[1:]

            fig.add_trace(Bar(name=name, x=dates, y=ys, marker={"color": color[column]}))

        fig.update_layout(barmode="stack", title_text=engine.plural(whatever).capitalize())

        fig.update_traces(marker_line_width=0)

        return fig
Exemplo n.º 14
0
    def _fig_layout(fig: go.Figure) -> go.Figure:
        """ Modify the figure to match the desired style

        :param fig: figure to be formatted
        :return: formatted figure
        """
        fig.update_layout(xaxis=dict(
            showline=True,
            showgrid=False,
            showticklabels=True,
            linecolor='rgb(204, 204, 204)',
            linewidth=2,
            ticks='outside',
            tickfont=dict(
                family='Arial',
                size=12,
                color='rgb(82, 82, 82)',
            ),
        ),
                          yaxis=dict(
                              showgrid=False,
                              zeroline=False,
                              showline=False,
                              showticklabels=False,
                          ),
                          autosize=True,
                          showlegend=False,
                          margin=dict(
                              autoexpand=False,
                              l=20,
                              r=20,
                              t=110,
                          ),
                          plot_bgcolor='white')
        return fig
def build(z: List[List[float]],
          scale: Union[str, List[List[Union[float, str]]]], path: str):
    """
    Тут будується графік, після чого зображення зберігається у файл.
    """
    figure = Figure()
    figure.add_trace(
        Heatmap(z=z, zmin=0, zmax=1, colorscale=scale, showscale=False))
    figure.update_layout(showlegend=False,
                         plot_bgcolor='white',
                         margin={
                             't': 0,
                             'r': 0,
                             'b': 0,
                             'l': 0
                         })
    figure.update_xaxes(showticklabels=False,
                        showgrid=False,
                        showline=False,
                        zeroline=False)
    figure.update_yaxes(scaleanchor='x',
                        scaleratio=1,
                        showticklabels=False,
                        showgrid=False,
                        showline=False,
                        zeroline=False)
    figure.write_image(path, width=1600, height=800)
Exemplo n.º 16
0
def configure_upset_plot_axes(fig: go.Figure, set_intersections: pd.DataFrame,
                              truncated: bool, title: str) -> go.Figure:
    """
    Format and organise plot axes for upset plotly figure
    :param fig: a go.Figure containing the plotly upsetplot
    :param set_intersections: pandas DataFrame with all set intersections
    :param truncated: boolean if the number of intersections has been truncated
                      or not (i.e. if num intersections > MAX_UPSET_INTERSECTIONS)
    :param title: str containing the plot title
    :return: A go.Figure of the plotly upsetplot with tidied/formatted
             axes legends
    """

    if truncated:
        plot_label = f"{title} UpSet Plot<br>(Truncated to {MAX_UPSET_INTERSECTIONS} Most "\
                      "Common Intersections)"
    else:
        plot_label = f"{title} UpSet Plot<br>(All Intersections)"

    fig.update_layout(
        dict(
            title=plot_label,

            # tidy axes for cardinality plot
            xaxis1=dict(showticklabels=False, fixedrange=True),
            yaxis1=dict(title="Set Count"),

            # grid
            xaxis2=dict(showticklabels=False,
                        fixedrange=True,
                        range=[-0.5,
                               len(set_intersections.columns) - 0.5]),
            yaxis2=dict(showticklabels=False,
                        fixedrange=True,
                        range=[-0.5, len(set_intersections.index) + 0.5]),

            # membership
            xaxis3=dict(showticklabels=False,
                        fixedrange=True,
                        range=[-0.5,
                               len(set_intersections.columns) - 0.5]),
            yaxis3=dict(
                tickmode='array',
                tickvals=list(range(len(set_intersections.index))),
                ticktext=set_intersections.index,
                title=f"{title}",
                fixedrange=True,
                range=[-0.5, len(set_intersections.index) + 0.5],
                tickfont=dict(size=10),
                automargin=True,
            ),

            # category count
            xaxis4=dict(title=f"Unique Count of<br>{title}"),
            yaxis4=dict(showticklabels=False,
                        fixedrange=True,
                        range=[-0.5, len(set_intersections.index) + 0.5])))
    return fig
def set_layout_size(fig: go.Figure, width: int, height: int,
    hsplit: int = 1, vsplit: int = 1) -> None:
    fig.update_layout(
        dict(
            autosize=False,
            width=width / hsplit,
            height=height / vsplit,
        )
    )
Exemplo n.º 18
0
 def layout_axes(self, fig: go.Figure) -> None:
     """Configure my x and y axis settings in fig."""
     fig.update_layout({
         self.xaxis_id: dict(anchor='y' + self.axis_id),
         self.yaxis_id: dict(anchor='x' + self.axis_id)
     })
     fig.layout[self.xaxis_id].update(domain=self.x_domain,
                                      **self.axis_defaults,
                                      **self.xaxis_props)
     fig.layout[self.yaxis_id].update(domain=self.y_domain,
                                      **self.axis_defaults,
                                      **self.yaxis_props)
Exemplo n.º 19
0
def build_mse_dependency(
        distribution_name: str, thetas: List[float], n_moments: int, n_samples: int, n_runs: int
) -> Figure:
    figure = Figure()
    figure.update_layout(title=f"Dependency between moment and MSE for {distribution_name} distribution")
    figure.update_xaxes(title="k")
    figure.update_yaxes(title="MSE")
    for theta in thetas:
        experiment = Experiment(distribution_name, theta)
        mse = experiment.run(n_moments, n_samples, n_runs)
        figure.add_trace(create_mse_scatter(mse, distribution_name, theta))
    return figure
Exemplo n.º 20
0
    def add_to_plot(
            fig: go.Figure, y: List[float], y_labels: Optional[List[str]] = None, y_err: Optional[List[float]] = None,
            practical_maximum: Optional[float] = None, label: Optional[str] = None, index: int = 0):
        color = QUALITATIVE_COLORS[index % len(QUALITATIVE_COLORS)]
        rgb_ints = [str(int(x.strip(" "))) for x in color[4:][:-1].split(",")]
        new_color = f"rgba({','.join(rgb_ints + ['0.2'])})"

        custom_data: Optional[List[Any]] = None
        hover_template = None
        if y_labels:
            if y_err:
                custom_data: List[Any] = list(zip(y_labels, y_err))
                hover_template = '<b>%{y:.3f} +- %{customdata[1]:.3f}</b><br>%{customdata[0]}'
            else:
                custom_data = y_labels
                hover_template = '<b>%{y:.3f}</b><br>%{customdata}'

        # Error range
        x = list(range(1, len(y) + 1))
        if y_err:
            y_upper = [m + e for m, e in zip(y, y_err)]
            y_lower = [m - e for m, e in zip(y, y_err)]
            fig.add_trace(go.Scatter(
                x=x + x[::-1],
                y=y_upper + y_lower[::-1],
                fill='toself',
                fillcolor=new_color,
                line_color='rgba(255,255,255,0)',
                showlegend=False,
                name=label,
                hoverinfo='skip',
                legendgroup=label,
            ))

        # Mean line
        fig.add_trace(go.Scatter(
            x=x, y=y,
            customdata=custom_data,
            hovertemplate=hover_template,
            line_color=color,
            showlegend=True,
            name=label,
            legendgroup=label,
        ))
        fig.update_traces(mode='lines')

        # Make serif
        fig.update_layout(font=dict(
            family="serif",
        ))

        return fig
Exemplo n.º 21
0
def get_figure_of_graph_bar_plot_number_of_scenes(df, xaxis_range=[], title=None):

    figure_height = 800
    df_copy = df.copy()

    logging.info(f'get_figure_of_graph_bar_plot_number_of_scenes - df_copy.head(): \n{df_copy.head()}\n')
    logging.info(f'get_figure_of_graph_bar_plot_number_of_scenes - xaxis_range: {xaxis_range}\n')

    logical_date_range = __get_logical_date_range(df_copy, xaxis_range)

    # I'm goint to build the `data` parameter of `Figure`
    data = []

    # I would like to build each `bar` based on each dataset
    for dataset in df_copy['collection'].unique():
        sub_df = df_copy[(df_copy['collection'] == dataset) & logical_date_range]

        hovertext = 'Number of Scenes: ' + sub_df['number'].map(str) + '<br>' + \
                    'Period: ' + sub_df['year_month'].map(str) + '<br>' + \
                    'Dataset: ' + sub_df['collection'].map(str)

        data.append(Bar({
            'x': sub_df['year_month'],
            'y': sub_df['number'],
            'name': dataset,
            'text': sub_df['number'],  # text inside the bar
            'textposition': 'auto',
            'hovertext': hovertext,
        }))

    fig = Figure({
        'data': data,
        'layout': {
            'title': title,
            'xaxis': {'title': 'Period'},
            'yaxis': {'title': 'Number of scenes'},
            'plot_bgcolor': colors['background'],
            'paper_bgcolor': colors['background'],
            'font': {
                'color': colors['text']
            }
        }
    })

    fig.update_layout(
        barmode='group',
        height=figure_height,
        xaxis_tickangle=-45
    )

    return fig
Exemplo n.º 22
0
def save_figure(figure: go.Figure, output_name: str, output_dir: str,
                output_size: Tuple[int, int]) -> None:
    """Saves figure into png image file.

    Args:
        figure: Figure to save.
        output_name: Output filename.
        output_dir: Output directory.
        output_size: Size of saved image.

    """
    output = str(Path(output_dir) / (output_name.replace(" ", "_") + ".png"))
    figure.update_layout(width=output_size[0], height=output_size[1])
    figure.write_image(output, engine="kaleido")
Exemplo n.º 23
0
def end_to_end_plot(
    fig: go.Figure,
    df: pd.DataFrame,
    data_column: str,
    group_by_columns: List[str],
    title_text: str,
) -> go.Figure:
    df = concat_columns_for_color(wnc(df, data_column), group_by_columns)
    df = prepare_for_plotting(df, data_column)
    plot_with_color(fig, df, data_column)
    fig.update_layout(xaxis_type="log",
                      title_text=title_text,
                      xaxis_title="lambda + 1")
    return fig
Exemplo n.º 24
0
def render_table(cells: List[List], header: List[str], title: str) -> Figure:
    header_dict = dict(
        values=header,
        line_color='white',
        fill_color=qualitative.T10[2],
        align='left',
        font=dict(
            color='white',
            family="Lato",
            size=20,
        ),
        height=30,
    )

    cells_dict = dict(
        values=cells,
        line_color='white',
        fill_color=qualitative.G10[9],
        align='left',
        font=dict(
            color='white',
            family="Lato",
            size=20,
        ),
        height=30,
    )

    table = Table(
        header=header_dict,
        cells=cells_dict,
    )

    figure = Figure(
        data=[table],
    )

    figure.update_layout(
        margin=dict(l=20, r=20, t=100, b=20),
        paper_bgcolor=qualitative.G10[9],
        font_family="Late",
        font_color="white",
        title_text=title,
        title_x=0.5,
        title_font_family="Late",
        title_font_color="white",
        title_font_size=30,
    )

    return figure
Exemplo n.º 25
0
def customize_cartesian_plot(fig: go.Figure) -> go.Figure:
    """Update the hover-mode and margin specifically for cartesian plots e.g.
    line-plots & bar-plots; further to `customize figure`.

    Args:
        fig (plotly.graph_objs._figure.Figure): Figure to edit.

    Returns:
        plotly.graph_objs._figure.Figure: Updated figure.
    """
    fig = customize_figure(fig)
    fig.update_layout(
        hovermode="x unified",
        margin={"l": 60, "t": 60, "r": 10, "b": 10},
    )
    return fig
 def __update_layout(self, fig: pgo.Figure):
     fig.update_layout(
         dict1={
             'title': None if not self.properties.show_title else {
                 'text': self.properties.title,
             },
             'showlegend': self.properties.show_legend,
             'margin': {
                 'l': 60,
                 'r': 30,
                 't': 30,
                 'b': 60,
             }
         },
         overwrite=True
     )
Exemplo n.º 27
0
def update_layout(
    fig: go.Figure,
    column: STATISTICS_KEY,
    x_category_order: PLOTTY_CATEGORY_ORDER = PLOTTY_CATEGORY_ORDER.
    TOTAL_ASCENDING
) -> go.Figure:
    # x_category_order='total ascending' means: in order of increasing values in Y
    # x_category_order='category ascending' means: in order of increasing values in X
    fig.update_layout(
        yaxis=dict(title_text=STATISTICS_SHOWING_KEY.FREQ.value),
        xaxis=dict(
            title_text=get_readable_key(column.value),
            # We use type = 'category' because we want to display all values (numbers and strings)
            type='category',
            categoryorder=x_category_order.value),
        plot_bgcolor=STATISTICS_COLORS.BAR_CHART_BG.value)
    return fig
Exemplo n.º 28
0
def customize_figure(fig: go.Figure) -> go.Figure:
    """Update the font style, plot background, axes labels, and more.

    Args:
        fig (plotly.graph_objs._figure.Figure): Figure to edit.

    Returns:
        plotly.graph_objs._figure.Figure: Customized figure.
    """
    fig.update_layout(
        font_family="serif",
        plot_bgcolor="#fff",
        titlefont=dict(size=18, color="#444"),
        title_x=0.1,
    )
    fig.update_xaxes(fixedrange=True, title_font_size=16)
    fig.update_yaxes(fixedrange=True, title_font_size=16)
    return fig
Exemplo n.º 29
0
    def plot_layers(self, fig: go.Figure):
        """ Plot layers """

        layer_names = []
        layer: AbstractLayer
        for layer in self.layers:
            layer.plot(fig)
            layer_names.append(layer.name)

        # Set layer names as x axis ticks
        x_axis = dict(tickmode='array',
                      tickvals=np.arange(len(layer_names)),
                      ticktext=layer_names)

        fig.update_layout(showlegend=False,
                          xaxis=x_axis,
                          clickmode='event+select',
                          template=self.theme.plotly)
Exemplo n.º 30
0
def generate(data, to_json=False, mode="freeform"):
    """Generate graph data and show in browser."""
    labels, source, target, value, hovers = [], [], [], [], []
    indexes = {}
    indexes[json.dumps(None)] = 0
    labels.append("")
    # data = find_json(data)
    for idx, (thing, parent, _) in enumerate(walker(data), 1):
        indexes[json.dumps(thing)] = idx
        label = ("{}".format(thing.get("__label", "Unknown")))
        labels.append(label)
        source.append(idx)
        target.append(indexes[json.dumps(parent)])
        value.append(max((thing["__cost"], 0.00001)))  # 0.0 wouldn't plot
        hovers.append(prettify_details(thing))

    fig = Figure(data=[
        Sankey(ids=labels,
               arrangement=mode,
               node=dict(
                   pad=15,
                   thickness=20,
                   line=dict(color="black", width=0.5),
                   label=labels,
                   color=["green"] + ["blue"] * (len(labels) - 1),
               ),
               link=dict(source=source,
                         target=target,
                         value=value,
                         label=hovers,
                         hoverlabel=dict(font=dict(
                             family="Courier New, monospace"))))
    ])

    fig.update_layout(title_text="Execution plan",
                      margin=dict(l=0, r=0, t=0, b=0),
                      font_size=12,
                      font_family="monospace")
    if to_json:
        return json.dumps([fig], cls=utils.PlotlyJSONEncoder)
    else:
        fig.show()
    return None