Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve outline of location names #96

Open
BrunoRosendo opened this issue May 21, 2024 · 1 comment
Open

Improve outline of location names #96

BrunoRosendo opened this issue May 21, 2024 · 1 comment
Labels
wontfix This will not be worked on

Comments

@BrunoRosendo
Copy link
Owner

Plotly doesn't have an outline for their fonts so I had do improvise and place a bigger text underneath the original one.
This does not look the best, I could think about injecting HTML directly in a dash app (see chatgpt dump)

import dash
from dash import dcc, html
import plotly.graph_objects as go
from pathlib import Path

class SolutionPlotter:
    COLOR_LIST = [
        "blue", "red", "green", "purple", "orange", "yellow", "pink", 
        "brown", "grey", "black", "cyan", "magenta"
    ]

    def __init__(self, num_vehicles, routes, locations, location_names, capacities, use_capacity, total_distance, depot, use_depot):
        self.num_vehicles = num_vehicles
        self.routes = routes
        self.locations = locations
        self.location_names = location_names
        self.capacities = capacities
        self.use_capacity = use_capacity
        self.total_distance = total_distance
        self.depot = depot
        self.use_depot = use_depot

    def display(self, file_name: str = None, results_path: str = "results"):
        """
        Display the solution using a plotly figure.
        Saves the figure to an HTML file if a file name is provided.
        """

        fig = go.Figure()

        for vehicle_id in range(self.num_vehicles):
            route_coordinates = [
                (self.locations[node][0], self.locations[node][1])
                for node in self.routes[vehicle_id]
            ]

            color = self.COLOR_LIST[vehicle_id % len(self.COLOR_LIST)]

            capacity = (
                self.capacities[vehicle_id]
                if isinstance(self.capacities, list)
                else self.capacities
            )
            legend_group = f"Vehicle {vehicle_id + 1}"
            legend_name = (
                f"Vehicle {vehicle_id + 1} ({capacity})"
                if self.use_capacity
                else f"Vehicle {vehicle_id + 1}"
            )

            # Draw routes
            fig.add_trace(
                go.Scatter(
                    x=[loc[0] for loc in route_coordinates],
                    y=[loc[1] for loc in route_coordinates],
                    mode="lines",
                    line=dict(width=5, color=color),
                    name=legend_name,
                    legendgroup=legend_group,
                )
            )

            # Draw annotations
            for i in range(len(route_coordinates) - 1):
                loc_name = self.location_names[self.routes[vehicle_id][i]]

                self.plot_direction(
                    fig,
                    route_coordinates[i],
                    route_coordinates[i + 1],
                    color,
                    5,
                    legend_group,
                )
                self.plot_location(
                    fig,
                    route_coordinates[i],
                    color,
                    loc_name,
                    legend_group,
                    vehicle_id,
                    i,
                )

            if not self.use_depot and len(route_coordinates) > 0:
                self.plot_location(
                    fig,
                    route_coordinates[-1],
                    color,
                    self.location_names[self.routes[vehicle_id][-1]],
                    legend_group,
                    vehicle_id,
                    len(route_coordinates) - 1,
                )

        if self.use_depot:
            self.plot_location(
                fig, self.locations[self.depot], "gray", self.location_names[self.depot]
            )

        fig.update_layout(
            xaxis_title="X Coordinate",
            yaxis_title="Y Coordinate",
            legend=dict(
                title=f"Total Distance: {self.total_distance}m",
                orientation="h",
                yanchor="bottom",
                y=1.02,
            ),
        )

        app = dash.Dash(__name__)
        app.layout = html.Div(
            [
                dcc.Graph(id="scatter-plot", figure=fig),
                # Add HTML styled text for each location
                *[
                    html.Div(
                        loc_name,
                        style={
                            "position": "absolute",
                            "left": f"{loc[0]}px",
                            "top": f"{loc[1]}px",
                            "transform": "translate(-50%, -50%)",
                            "color": "white",
                            "font-size": "15px",
                            "text-shadow": "-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000",
                        },
                    )
                    for loc, loc_name in zip(self.locations, self.location_names)
                ]
            ],
            style={"position": "relative", "width": "100%", "height": "100%"},
        )

        app.run_server(debug=True)

        if file_name is not None:
            html_path = f"{results_path}/html"
            Path(html_path).mkdir(parents=True, exist_ok=True)
            fig.write_html(f"{html_path}/{file_name}.html")

    def plot_direction(self, fig, loc1, loc2, color, line_width, legend_group=None):
        """
        Plot an arrow representing the direction from coord1 to coord2 with the given color and line width.
        """
        x_mid = (loc1[0] + loc2[0]) / 2
        y_mid = (loc1[1] + loc2[1]) / 2

        fig.add_trace(
            go.Scatter(
                x=[loc1[0], x_mid],
                y=[loc1[1], y_mid],
                mode="lines+markers",
                line=dict(width=line_width, color=color),
                marker=dict(size=20, symbol="arrow-up", angleref="previous"),
                hoverinfo="skip",
                showlegend=False,
                legendgroup=legend_group,
            )
        )

    def plot_location(
        self,
        fig,
        loc,
        color,
        name,
        legend_group=None,
        vehicle_id=None,
        route_id=None,
    ):
        """
        Plot a location with the given color and legend group.
        """
        hovertext = (
            "Starting Point"
            if vehicle_id is None
            else (
                f"Vehicle {vehicle_id + 1}: {self.loads[vehicle_id][route_id]} passengers"
                if self.use_capacity
                else f"Vehicle {vehicle_id + 1}"
            )
        )

        fig.add_trace(
            go.Scatter(
                x=[loc[0]],
                y=[loc[1]],
                mode="markers+text",
                marker=dict(size=50, symbol="circle", color=color, line_width=2),
                text=name,
                textposition="middle center",
                textfont=dict(color="white", size=15),
                showlegend=False,
                hoverinfo="text",
                hovertext=hovertext,
                legendgroup=legend_group,
            )
        )
@BrunoRosendo BrunoRosendo added wontfix This will not be worked on and removed P2 labels Aug 19, 2024
@BrunoRosendo
Copy link
Owner Author

I think it is fine as is, changing into wontfixe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

1 participant