예제 #1
0
class DropdownWithEdit(ipywidgets.Widget):
    """
    Dropdown, but displays an edit button if the current selection is
    a parameterized object.
    """

    # I couldn't figure out which widget class actually declares the
    # value trait. Might not be needed in ipywidgets>6 because I
    # can see value trait declared in ValueWidget...
    value = traitlets.Any()

    def __init__(self, *args, **kwargs):
        self._select = Dropdown(*args, **kwargs)
        self._edit = Button(description='...', layout=Layout(width='15px'))
        self._composite = HBox([self._select, self._edit])
        super(DropdownWithEdit, self).__init__()
        self.layout = self._composite.layout
        # so that others looking at this widget's value get the
        # dropdown's value
        traitlets.link((self._select, 'value'), (self, 'value'))
        self._edit.on_click(lambda _: editor(self._select.value))
        self._select.observe(lambda e: self._set_editable(e['new']), 'value')
        self._set_editable(self._select.value)

    def _set_editable(self, v):
        if hasattr(v, 'params'):
            self._edit.layout.display = None  # i.e. make it visible
        else:
            self._edit.layout.display = 'none'

    def _ipython_display_(self, **kwargs):
        self._composite._ipython_display_(**kwargs)

    def get_state(self, *args, **kw):
        # support layouts; see CrossSelect.get_state
        return self._composite.get_state(*args, **kw)
예제 #2
0
class MultiProgressWidget(MultiProgress):
    """ Multiple progress bar Widget suitable for the notebook

    Displays multiple progress bars for a computation, split on computation
    type.

    See Also
    --------
    progress: User-level function <--- use this
    MultiProgress: Non-visualization component that contains most logic
    ProgressWidget: Single progress bar widget
    """
    def __init__(self, keys, scheduler=None, minimum=0, dt=0.1, func=key_split,
                 complete=False):
        self.func = func
        keys = {k.key if hasattr(k, 'key') else k for k in keys}
        self.setup_pre(keys, scheduler, minimum, dt, complete)

        def clear_errors(errors):
            for k in errors:
                self.task_erred(None, k, None, True)

        # Get keys and all-keys
        if self.scheduler.loop._thread_ident == threading.current_thread().ident:
            errors = self.setup(keys, complete)
        else:
            errors = sync(self.scheduler.loop, self.setup, keys, complete)

        # Set up widgets
        from ipywidgets import FloatProgress, VBox, HTML, HBox
        self.bars = {key: FloatProgress(min=0, max=1, description=key)
                        for key in self.all_keys}
        self.texts = {key: HTML() for key in self.all_keys}
        self.boxes = {key: HBox([self.bars[key], self.texts[key]])
                        for key in self.all_keys}
        self.time = HTML()
        self.widget = HBox([self.time, VBox([self.boxes[key] for key in
                                            sorted(self.bars, key=str)])])

        from tornado.ioloop import IOLoop
        loop = IOLoop.instance()
        self.pc = PeriodicCallback(self._update, 1000 * self._dt, io_loop=loop)
        self.pc.start()

        # Clear out errors
        clear_errors(errors)

    def _start(self):
        return self._update()

    def _ipython_display_(self, **kwargs):
        return self.widget._ipython_display_(**kwargs)

    def stop(self, exception=None, key=None):
        with ignoring(AttributeError):
            self.pc.stop()
        Progress.stop(self, exception, key)
        self._update()
        for k, v in self.keys.items():
            if not v:
                self.bars[k].bar_style = 'success'
        if exception:
            self.bars[self.func(key)].value = 1
            self.bars[self.func(key)].bar_style = 'danger'

    def _update(self):
        for k in self.all_keys:
            ntasks = len(self.all_keys[k])
            ndone = ntasks - len(self.keys[k])
            self.bars[k].value = ndone / ntasks if ntasks else 1.0
            self.texts[k].value = "%d / %d" % (ndone, ntasks)
            self.time.value = format_time(self.elapsed)
예제 #3
0
class TrafficWidget(object):

    # -- Constructor --
    def __init__(self, traffic: Traffic, projection=EuroPP()) -> None:

        ipython = get_ipython()
        ipython.magic("matplotlib ipympl")
        from ipympl.backend_nbagg import FigureCanvasNbAgg, FigureManagerNbAgg

        self.fig_map = Figure(figsize=(6, 6))
        self.fig_time = Figure(figsize=(6, 4))

        self.canvas_map = FigureCanvasNbAgg(self.fig_map)
        self.canvas_time = FigureCanvasNbAgg(self.fig_time)

        self.manager_map = FigureManagerNbAgg(self.canvas_map, 0)
        self.manager_time = FigureManagerNbAgg(self.canvas_time, 0)

        layout = {"width": "590px", "height": "800px", "border": "none"}
        self.output = Output(layout=layout)

        self._traffic = traffic
        self.t_view = traffic.sort_values("timestamp")
        self.trajectories: Dict[str, List[Artist]] = defaultdict(list)

        self.create_map(projection)

        self.projection = Dropdown(options=["EuroPP", "Lambert93", "Mercator"])
        self.projection.observe(self.on_projection_change)

        self.identifier_input = Text(description="Callsign/ID")
        self.identifier_input.observe(self.on_id_input)

        self.identifier_select = SelectMultiple(
            options=sorted(self._traffic.callsigns),  # type: ignore
            value=[],
            rows=20,
        )
        self.identifier_select.observe(self.on_id_change)

        self.area_input = Text(description="Area")
        self.area_input.observe(self.on_area_input)

        self.extent_button = Button(description="Extent")
        self.extent_button.on_click(self.on_extent_button)

        self.plot_button = Button(description="Plot")
        self.plot_button.on_click(self.on_plot_button)

        self.clear_button = Button(description="Reset")
        self.clear_button.on_click(self.on_clear_button)

        self.plot_airport = Button(description="Airport")
        self.plot_airport.on_click(self.on_plot_airport)

        self.area_select = SelectMultiple(options=[],
                                          value=[],
                                          rows=3,
                                          disabled=False)
        self.area_select.observe(self.on_area_click)

        self.altitude_select = SelectionRangeSlider(
            options=[0, 5000, 10000, 15000, 20000, 25000, 30000, 35000, 40000],
            index=(0, 8),
            description="Altitude",
            disabled=False,
            continuous_update=False,
        )
        self.altitude_select.observe(self.on_altitude_select)

        self.time_slider = SelectionRangeSlider(
            options=list(range(100)),
            index=(0, 99),
            description="Date",
            continuous_update=False,
        )
        self.lock_time_change = False
        self.set_time_range()

        self.time_slider.observe(self.on_time_select)
        self.canvas_map.observe(self.on_axmap_change,
                                ["_button", "_png_is_old"])
        self.canvas_time.observe(self.on_axtime_change, ["_png_is_old"])

        col_options = []
        for column, dtype in self._traffic.data.dtypes.items():
            if column not in ("latitude", "longitude"):
                if dtype in ["float64", "int64"]:
                    col_options.append(column)

        self.y_selector = SelectMultiple(options=col_options,
                                         value=[],
                                         rows=5,
                                         disabled=False)
        self.y_selector.observe(self.on_id_change)

        self.sec_y_selector = SelectMultiple(options=col_options,
                                             value=[],
                                             rows=5,
                                             disabled=False)
        self.sec_y_selector.observe(self.on_id_change)

        self.time_tab = VBox(
            [HBox([self.y_selector, self.sec_y_selector]), self.canvas_time])

        self.tabs = Tab()
        self.tabs.children = [self.canvas_map, self.time_tab]
        self.tabs.set_title(0, "Map")
        self.tabs.set_title(1, "Plots")

        self._main_elt = HBox([
            self.tabs,
            VBox([
                self.projection,
                HBox([self.extent_button, self.plot_button]),
                HBox([self.plot_airport, self.clear_button]),
                self.area_input,
                self.area_select,
                self.time_slider,
                self.altitude_select,
                self.identifier_input,
                self.identifier_select,
            ]),
        ])

    @property
    def traffic(self) -> Traffic:
        return self._traffic

    def _ipython_display_(self) -> None:
        clear_output()
        self.canvas_map.draw_idle()
        self._main_elt._ipython_display_()

    def debug(self) -> None:
        if self.tabs.children[-1] != self.output:
            self.tabs.children = list(self.tabs.children) + [self.output]

    def set_time_range(self) -> None:
        with self.output:
            tz_now = datetime.now().astimezone().tzinfo
            self.dates = [
                self._traffic.start_time + i *
                (self._traffic.end_time - self._traffic.start_time) / 99
                for i in range(100)
            ]
            if self._traffic.start_time.tzinfo is not None:
                options = [
                    t.tz_convert("utc").strftime("%H:%M") for t in self.dates
                ]
            else:
                options = [
                    t.tz_localize(tz_now).tz_convert("utc").strftime("%H:%M")
                    for t in self.dates
                ]

            self.lock_time_change = True
            self.time_slider.options = options
            self.time_slider.index = (0, 99)
            self.lock_time_change = False

    def create_map(
        self,
        projection: Union[str, Projection] = "EuroPP()"  # type: ignore
    ) -> None:
        with self.output:
            if isinstance(projection, str):
                if not projection.endswith("()"):
                    projection = projection + "()"
                projection = eval(projection)

            self.projection = projection

            with plt.style.context("traffic"):

                self.fig_map.clear()
                self.trajectories.clear()
                self.ax_map = self.fig_map.add_subplot(
                    111, projection=self.projection)
                self.ax_map.add_feature(countries())
                if projection.__class__.__name__.split(".")[-1] in [
                        "Lambert93"
                ]:
                    self.ax_map.add_feature(rivers())

                self.fig_map.set_tight_layout(True)
                self.ax_map.background_patch.set_visible(False)
                self.ax_map.outline_patch.set_visible(False)
                self.ax_map.format_coord = lambda x, y: ""
                self.ax_map.set_global()

            self.default_plot()
            self.canvas_map.draw_idle()

    def default_plot(self) -> None:
        with self.output:
            # clear all trajectory pieces
            for key, value in self.trajectories.items():
                for elt in value:
                    elt.remove()
            self.trajectories.clear()
            self.ax_map.set_prop_cycle(None)

            lon_min, lon_max, lat_min, lat_max = self.ax_map.get_extent(
                PlateCarree())
            cur_flights = list(
                f.at() for f in self.t_view
                if lat_min <= getattr(f.at(), "latitude", -90) <= lat_max
                and lon_min <= getattr(f.at(), "longitude", -180) <= lon_max)

            def params(at):
                if len(cur_flights) < 10:
                    return dict(s=8, text_kw=dict(s=at.callsign))
                else:
                    return dict(s=8, text_kw=dict(s=""))

            for at in cur_flights:
                if at is not None:
                    self.trajectories[at.callsign] += at.plot(
                        self.ax_map, **params(at))

            self.canvas_map.draw_idle()

    def create_timeplot(self) -> None:
        with plt.style.context("traffic"):
            self.fig_time.clear()
            self.ax_time = self.fig_time.add_subplot(111)
            self.fig_time.set_tight_layout(True)

    # -- Callbacks --

    def on_projection_change(self, change: Dict[str, Any]) -> None:
        with self.output:
            if change["name"] == "value":
                self.create_map(change["new"])

    def on_clear_button(self, elt: Dict[str, Any]) -> None:
        with self.output:
            self.t_view = self.traffic.sort_values("timestamp")
            self.create_map(self.projection)
            self.create_timeplot()

    def on_area_input(self, elt: Dict[str, Any]) -> None:
        with self.output:
            if elt["name"] != "value":
                return
            search_text = elt["new"]
            if len(search_text) == 0:
                self.area_select.options = list()
            else:
                from ..data import airac

                self.area_select.options = list(
                    x.name for x in airac.parse(search_text))

    def on_area_click(self, elt: Dict[str, Any]) -> None:
        with self.output:
            if elt["name"] != "value":
                return
            from ..data import airac

            self.ax_map.set_extent(airac[elt["new"][0]])
            self.canvas_map.draw_idle()

    def on_extent_button(self, elt: Dict[str, Any]) -> None:
        with self.output:
            if len(self.area_select.value) == 0:
                if len(self.area_input.value) == 0:
                    self.ax_map.set_global()
                else:
                    self.ax_map.set_extent(location(self.area_input.value))
            else:
                from ..data import airac

                self.ax_map.set_extent(airac[self.area_select.value[0]])

            t1, t2 = self.time_slider.index
            low, up = self.altitude_select.value
            self.on_filter(low, up, t1, t2)
            self.canvas_map.draw_idle()

    def on_axtime_change(self, change: Dict[str, Any]) -> None:
        with self.output:
            if change["name"] == "_png_is_old":
                # go away!!
                return self.canvas_map.set_window_title("")

    def on_axmap_change(self, change: Dict[str, Any]) -> None:
        with self.output:
            if change["name"] == "_png_is_old":
                # go away!!
                return self.canvas_map.set_window_title("")
            if change["new"] is None:
                t1, t2 = self.time_slider.index
                low, up = self.altitude_select.value
                self.on_filter(low, up, t1, t2)

    def on_id_input(self, elt: Dict[str, Any]) -> None:
        with self.output:
            # typing issue because of the lru_cache_wrappen
            callsigns = cast(Set[str], self.t_view.callsigns)
            # low, up = alt.value
            self.identifier_select.options = sorted(
                callsign for callsign in callsigns if re.match(
                    elt["new"]["value"], callsign, flags=re.IGNORECASE))

    def on_plot_button(self, elt: Dict[str, Any]) -> None:
        with self.output:
            if len(self.area_select.value) == 0:
                if len(self.area_input.value) == 0:
                    return self.default_plot()
                location(self.area_input.value).plot(self.ax_map,
                                                     color="grey",
                                                     linestyle="dashed")
            else:
                from ..data import airac

                airspace = airac[self.area_select.value[0]]
                if airspace is not None:
                    airspace.plot(self.ax_map)
            self.canvas_map.draw_idle()

    def on_plot_airport(self, elt: Dict[str, Any]) -> None:
        with self.output:
            if len(self.area_input.value) == 0:
                from cartotools.osm import request, tags

                west, east, south, north = self.ax_map.get_extent(
                    crs=PlateCarree())
                if abs(east - west) > 1 or abs(north - south) > 1:
                    # that would be a too big request
                    return
                request((west, south, east, north),
                        **tags.airport).plot(self.ax_map)
            else:
                from ..data import airports

                airports[self.area_input.value].plot(self.ax_map)
            self.canvas_map.draw_idle()

    def on_id_change(self, change: Dict[str, Any]) -> None:
        with self.output:
            if change["name"] != "value":
                return

            y = self.y_selector.value + self.sec_y_selector.value
            secondary_y = self.sec_y_selector.value
            callsigns = self.identifier_select.value

            if len(y) == 0:
                y = ["altitude"]
            extra_dict = dict()
            if len(y) > 1:
                # just to avoid confusion...
                callsigns = callsigns[:1]

            # clear all trajectory pieces
            self.create_timeplot()
            for key, value in self.trajectories.items():
                for elt in value:
                    elt.remove()
            self.trajectories.clear()

            for callsign in callsigns:
                flight = self.t_view[callsign]
                if len(y) == 1:
                    extra_dict["label"] = callsign
                if flight is not None:
                    try:
                        self.trajectories[callsign] += flight.plot(self.ax_map)
                        at = flight.at()
                        if at is not None:
                            self.trajectories[callsign] += at.plot(
                                self.ax_map, s=8, text_kw=dict(s=callsign))
                    except Exception:  # NoneType object is not iterable
                        pass

                    try:
                        flight.plot_time(
                            self.ax_time,
                            y=y,
                            secondary_y=secondary_y,
                            **extra_dict,
                        )
                    except Exception:  # no numeric data to plot
                        pass

            if len(callsigns) == 0:
                self.default_plot()
            else:
                self.ax_time.legend()

            # non conformal with traffic style
            for elt in self.ax_time.get_xticklabels():
                elt.set_size(12)
            for elt in self.ax_time.get_yticklabels():
                elt.set_size(12)
            self.ax_time.set_xlabel("")

            self.canvas_map.draw_idle()
            self.canvas_time.draw_idle()

            if len(callsigns) != 0:
                low, up = self.ax_time.get_ylim()
                if (up - low) / up < 0.05:
                    self.ax_time.set_ylim(up - .05 * up, up + .05 * up)
                    self.canvas_time.draw_idle()

    def on_filter(self, low, up, t1, t2) -> None:
        with self.output:
            west, east, south, north = self.ax_map.get_extent(
                crs=PlateCarree())

            self.t_view = (self.traffic.between(
                self.dates[t1], self.dates[t2]).query(
                    f"{low} <= altitude <= {up} or altitude != altitude"
                ).query(
                    f"{west} <= longitude <= {east} and "
                    f"{south} <= latitude <= {north}").sort_values("timestamp")
                           )
            self.identifier_select.options = sorted(
                flight.callsign for flight in self.t_view
                if flight is not None and re.match(
                    self.identifier_input.value,
                    flight.callsign,
                    flags=re.IGNORECASE,
                ))
            return self.default_plot()

    def on_altitude_select(self, change: Dict[str, Any]) -> None:
        with self.output:
            if change["name"] != "value":
                return

            low, up = change["new"]
            t1, t2 = self.time_slider.index
            self.on_filter(low, up, t1, t2)

    def on_time_select(self, change: Dict[str, Any]) -> None:
        with self.output:
            if self.lock_time_change:
                return
            if change["name"] != "index":
                return
            t1, t2 = change["new"]
            low, up = self.altitude_select.value
            self.on_filter(low, up, t1, t2)