Ejemplo n.º 1
0
 def test_combine_latest(self):
     stream_1 = rx.Stream()
     stream_2 = rx.Stream()
     observer = unittest.mock.Mock()
     combined = rx.combine_latest(stream_1, stream_2)
     combined.subscribe(observer)
     stream_1.emit(1)
     observer.assert_called_once_with((1, None))
Ejemplo n.º 2
0
 def test_combine_latest_given_multiple_emits(self):
     stream_1 = rx.Stream()
     stream_2 = rx.Stream()
     observer = unittest.mock.Mock()
     combined = rx.combine_latest(stream_1, stream_2)
     combined.subscribe(observer)
     stream_1.emit(1)
     stream_2.emit(4)
     stream_1.emit(0)
     expect = [(1, None), (1, 4), (0, 4)]
     calls = [unittest.mock.call(args) for args in expect]
     observer.assert_has_calls(calls)
Ejemplo n.º 3
0
 def connect(self, store):
     """Convenient method to map state to props needed by render"""
     self.add_subscriber(store.dispatch)
     stream = (rx.Stream().listen_to(store).map(state_to_props).filter(
         lambda x: x is not None).distinct())
     stream.map(lambda props: self.render(*props))
     return self
Ejemplo n.º 4
0
 def test_unsubscribe(self):
     observer = unittest.mock.Mock()
     stream = rx.Stream()
     unsubscribe = stream.subscribe(observer)
     stream.emit((1, None))
     unsubscribe()
     stream.emit((2, None))
     observer.assert_called_once_with((1, None))
Ejemplo n.º 5
0
    def connect(self, store):
        """
        Add function from this object as a subscriber of changes in the
        position state.
        """

        stream = (rx.Stream().listen_to(store).map(self.to_props).distinct())
        stream.map(lambda props: self.place_marker(*props))
Ejemplo n.º 6
0
def test_stream_filter(values, predicate, expect):
    stream = rx.Stream()
    filtered = stream.filter(predicate)
    listener = unittest.mock.Mock()
    filtered.subscribe(listener)
    for value in values:
        stream.notify(value)
    calls = [unittest.mock.call(v) for v in expect]
    listener.assert_has_calls(calls)
Ejemplo n.º 7
0
def test_stream_distinct(values, expect):
    stream = rx.Stream()
    deduped = stream.distinct()
    listener = unittest.mock.Mock()
    deduped.subscribe(listener)
    for value in values:
        stream.notify(value)
    calls = [unittest.mock.call(v) for v in expect]
    listener.assert_has_calls(calls)
    assert listener.call_count == len(calls)
Ejemplo n.º 8
0
 def test_support_old_style_state(self):
     """Not all components are ready to accept dict() states"""
     listener = unittest.mock.Mock()
     store = redux.Store(forest.db.control.reducer)
     old_states = (rx.Stream().listen_to(store).map(
         lambda x: forest.db.control.State(**x)))
     old_states.add_subscriber(listener)
     store.dispatch(forest.db.control.set_value("pressure", 1000))
     expect = forest.db.control.State(pressure=1000)
     listener.assert_called_once_with(expect)
Ejemplo n.º 9
0
    def connect(self, store):
        """Connect component to store

        Converts state into stream of unique property changes suitable
        for use with render method
        """
        self.add_subscriber(store.dispatch)
        stream = (rx.Stream().listen_to(store).map(
            self.to_props).filter(lambda x: x is not None).distinct())
        stream.map(lambda props: self.render(*props))
        return self
Ejemplo n.º 10
0
 def connect(self, store):
     """Connect to the Store"""
     stream = (rx.Stream().listen_to(store).map(
         self.to_props).filter(lambda x: x is not None).distinct())
     stream.map(lambda props: self.render(*props))
Ejemplo n.º 11
0
def _connect(view, store):
    stream = (rx.Stream().listen_to(store).map(
        view.to_props).filter(lambda x: x is not None).distinct())
    stream.map(lambda props: view.render(*props))
Ejemplo n.º 12
0
def main(argv=None):

    args = parse_args.parse_args(argv)
    data.AUTO_SHUTDOWN = args.auto_shutdown

    if len(args.files) > 0:
        if args.config_file is not None:
            raise Exception(
                '--config-file and [FILE [FILE ...]] not compatible')
        config = cfg.from_files(args.files, args.file_type)
    else:
        config = cfg.Config.load(args.config_file,
                                 variables=cfg.combine_variables(
                                     os.environ, args.variables))

    # Feature toggles
    if "feature" in config.plugins:
        features = plugin.call(config.plugins["feature"].entry_point)
    else:
        features = config.features
    data.FEATURE_FLAGS = features

    # Full screen map
    viewport = config.default_viewport
    x_range, y_range = geo.web_mercator(viewport.lon_range, viewport.lat_range)
    figure = map_figure(x_range, y_range)
    figures = [figure]
    for _ in range(2):
        f = map_figure(figure.x_range, figure.y_range)
        figures.append(f)

    figure_row = layers.FigureRow(figures)

    color_mapper = bokeh.models.LinearColorMapper(
        low=0, high=1, palette=bokeh.palettes.Plasma[256])

    # Convert config to datasets
    datasets = {}
    datasets_by_pattern = {}
    label_to_pattern = {}
    for group in config.file_groups:
        settings = {
            "label": group.label,
            "pattern": group.pattern,
            "locator": group.locator,
            "database_path": group.database_path,
            "directory": group.directory
        }
        dataset = drivers.get_dataset(group.file_type, settings)
        datasets[group.label] = dataset
        datasets_by_pattern[group.pattern] = dataset
        label_to_pattern[group.label] = group.pattern

    layers_ui = layers.LayersUI()

    # Add optional sub-navigators
    sub_navigators = {
        key: dataset.navigator()
        for key, dataset in datasets_by_pattern.items()
        if hasattr(dataset, "navigator")
    }
    navigator = navigate.Navigator(sub_navigators)

    middlewares = [
        keys.navigate,
        db.InverseCoordinate("pressure"),
        db.next_previous,
        db.Controls(navigator),  # TODO: Deprecate this middleware
        colors.palettes,
        colors.middleware(),
        presets.Middleware(presets.proxy_storage(config.presets_file)),
        presets.middleware,
        layers.middleware,
        navigator,
        mws.echo,
    ]
    store = redux.Store(forest.reducer, middlewares=middlewares)

    app = forest.app.Application()
    app.add_component(forest.components.title.Title())

    # Coastlines, borders, lakes and disputed borders
    view = forest.components.borders.View()
    for figure in figures:
        view.add_figure(figure)
    view.connect(store)
    border_ui = forest.components.borders.UI()
    border_ui.connect(store)

    # Colorbar user interface
    component = forest.components.ColorbarUI()
    app.add_component(component)

    # Add time user interface
    if config.defaults.timeui:
        component = forest.components.TimeUI()
        component.layout = bokeh.layouts.row(component.layout, name="time")
        app.add_component(component)

    # Connect MapView orchestration to store
    opacity_slider = forest.layers.OpacitySlider()
    source_limits = colors.SourceLimits().connect(store)
    factory_class = forest.layers.factory(color_mapper, figures, source_limits,
                                          opacity_slider)
    gallery = forest.layers.Gallery.from_datasets(datasets, factory_class)
    gallery.connect(store)

    # Connect layers controls
    layers_ui.add_subscriber(store.dispatch)
    layers_ui.connect(store)

    # Connect tools controls

    display_names = {
        "time_series": "Display Time Series",
        "profile": "Display Profile"
    }
    available_features = {
        k: display_names[k]
        for k in display_names.keys() if data.FEATURE_FLAGS[k]
    }

    tools_panel = tools.ToolsPanel(available_features)
    tools_panel.connect(store)

    # Navbar components
    navbar = Navbar(show_diagram_button=len(available_features) > 0)
    navbar.connect(store)

    # Connect tap listener
    tap_listener = screen.TapListener()
    tap_listener.connect(store)

    # Connect figure controls/views
    if config.defaults.figures.ui:
        figure_ui = layers.FigureUI(config.defaults.figures.maximum)
        figure_ui.connect(store)
    figure_row.connect(store)

    # Tiling picker
    if config.use_web_map_tiles:
        tile_picker = forest.components.TilePicker()
        for figure in figures:
            tile_picker.add_figure(figure)
        tile_picker.connect(store)

    # Connect color palette controls
    colors.ColorMapperView(color_mapper).connect(store)
    color_palette = colors.ColorPalette().connect(store)

    # Connect limit controllers to store
    user_limits = colors.UserLimits().connect(store)

    # Preset
    if config.defaults.presetui:
        preset_ui = presets.PresetUI().connect(store)

    # Connect navigation controls
    controls = db.ControlView()
    controls.connect(store)

    # Add support for a modal dialogue
    if data.FEATURE_FLAGS["multiple_colorbars"]:
        view = forest.components.modal.Tabbed()
    else:
        view = forest.components.modal.Default()
    modal = forest.components.Modal(view=view)
    modal.connect(store)

    # Connect components to Store
    app.connect(store)

    # Set initial state
    store.dispatch(forest.actions.set_state(config.state).to_dict())

    # Pre-select menu choices (if any)
    for pattern, _ in sub_navigators.items():
        state = db.initial_state(navigator, pattern=pattern)
        store.dispatch(forest.actions.update_state(state).to_dict())
        break

    # Set default time series visibility
    store.dispatch(tools.on_toggle_tool("time_series", False))

    # Set default profile visibility
    store.dispatch(tools.on_toggle_tool("profile", False))

    # Set top-level navigation
    store.dispatch(db.set_value("patterns", config.patterns))

    # Pre-select first map_view layer
    for label, dataset in datasets.items():
        pattern = label_to_pattern[label]
        for variable in navigator.variables(pattern):
            spec = {
                "label": label,
                "dataset": label,
                "variable": variable,
                "active": [0]
            }
            store.dispatch(forest.layers.save_layer(0, spec))
            break
        break

    # Set variable dimensions (needed by modal dialogue)
    for label, dataset in datasets.items():
        pattern = label_to_pattern[label]
        values = navigator.variables(pattern)
        store.dispatch(dimension.set_variables(label, values))

    # Organise controls/settings
    layouts = {}
    layouts["controls"] = []
    if config.defaults.figures.ui:
        layouts["controls"] += [
            bokeh.models.Div(text="Layout:"), figure_ui.layout
        ]
    layouts["controls"] += [
        bokeh.models.Div(text="Navigate:"), controls.layout,
        bokeh.models.Div(text="Compare:"), layers_ui.layout
    ]

    layouts["settings"] = [
        border_ui.layout,
        opacity_slider.layout,
        color_palette.layout,
        user_limits.layout,
        bokeh.models.Div(text="Tiles:"),
    ]
    if config.defaults.presetui:
        layouts["settings"].insert(2, preset_ui.layout)
    if config.use_web_map_tiles:
        layouts["settings"].append(tile_picker.layout)

    tabs = bokeh.models.Tabs(tabs=[
        bokeh.models.Panel(child=bokeh.layouts.column(*layouts["controls"]),
                           title="Control"),
        bokeh.models.Panel(child=bokeh.layouts.column(*layouts["settings"]),
                           title="Settings")
    ])

    tool_figures = {}
    if data.FEATURE_FLAGS["time_series"]:
        # Series sub-figure widget
        series_figure = bokeh.plotting.figure(plot_width=400,
                                              plot_height=200,
                                              x_axis_type="datetime",
                                              toolbar_location=None,
                                              border_fill_alpha=0)
        series_figure.toolbar.logo = None

        series_view = series.SeriesView.from_groups(series_figure,
                                                    config.file_groups)
        series_view.add_subscriber(store.dispatch)
        series_args = (rx.Stream().listen_to(store).map(
            series.select_args).filter(lambda x: x is not None).distinct())
        series_args.map(lambda a: series_view.render(*a))
        series_args.map(print)  # Note: map(print) creates None stream

        tool_figures["series_figure"] = series_figure

    if data.FEATURE_FLAGS["profile"]:
        # Profile sub-figure widget
        profile_figure = bokeh.plotting.figure(plot_width=300,
                                               plot_height=450,
                                               toolbar_location=None,
                                               border_fill_alpha=0)
        profile_figure.toolbar.logo = None
        profile_figure.y_range.flipped = True

        profile_view = profile.ProfileView.from_groups(profile_figure,
                                                       config.file_groups)
        profile_view.add_subscriber(store.dispatch)
        profile_args = (rx.Stream().listen_to(store).map(
            profile.select_args).filter(lambda x: x is not None).distinct())
        profile_args.map(lambda a: profile_view.render(*a))
        profile_args.map(print)  # Note: map(print) creates None stream

        tool_figures["profile_figure"] = profile_figure

    tool_layout = tools.ToolLayout(**tool_figures)
    tool_layout.connect(store)

    for f in figures:
        f.on_event(bokeh.events.Tap, tap_listener.update_xy)
        marker = screen.MarkDraw(f).connect(store)

    control_root = bokeh.layouts.column(tabs, name="controls")

    # Add key press support
    key_press = keys.KeyPress()
    key_press.add_subscriber(store.dispatch)

    # Add HTML ready support
    obj = html_ready.HTMLReady(key_press.hidden_button)
    obj.connect(store)

    document = bokeh.plotting.curdoc()
    document.title = "FOREST"
    document.add_root(control_root)
    document.add_root(
        bokeh.layouts.column(tools_panel.layout,
                             tool_layout.layout,
                             width=400,
                             name="series"))
    for root in navbar.roots:
        document.add_root(root)
    for root in app.roots:
        document.add_root(root)
    document.add_root(figure_row.layout)
    document.add_root(key_press.hidden_button)
    document.add_root(modal.layout)
Ejemplo n.º 13
0
def main(argv=None):
    args = parse_args.parse_args(argv)
    if len(args.files) > 0:
        config = cfg.from_files(args.files, args.file_type)
    else:
        config = cfg.Config.load(args.config_file,
                                 variables=cfg.combine_variables(
                                     os.environ, args.variables))

    database = None
    if args.database is not None:
        if args.database != ':memory:':
            assert os.path.exists(args.database), "{} must exist".format(
                args.database)
        database = db.Database.connect(args.database)

    # Full screen map
    lon_range = (90, 140)
    lat_range = (-23.5, 23.5)
    x_range, y_range = geo.web_mercator(lon_range, lat_range)
    figure = bokeh.plotting.figure(x_range=x_range,
                                   y_range=y_range,
                                   x_axis_type="mercator",
                                   y_axis_type="mercator",
                                   active_scroll="wheel_zoom")
    tile = bokeh.models.WMTSTileSource(
        url="https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}.png",
        attribution="")

    figures = [figure]
    for _ in range(2):
        f = bokeh.plotting.figure(x_range=figure.x_range,
                                  y_range=figure.y_range,
                                  x_axis_type="mercator",
                                  y_axis_type="mercator",
                                  active_scroll="wheel_zoom")
        figures.append(f)

    for f in figures:
        f.axis.visible = False
        f.toolbar.logo = None
        f.toolbar_location = None
        f.min_border = 0
        f.add_tile(tile)

    figure_row = bokeh.layouts.row(*figures, sizing_mode="stretch_both")
    figure_row.children = [figures[0]]  # Trick to keep correct sizing modes

    figure_drop = bokeh.models.Dropdown(label="Figure",
                                        menu=[(str(i), str(i))
                                              for i in [1, 2, 3]])

    def on_change(attr, old, new):
        if int(new) == 1:
            figure_row.children = [figures[0]]
        elif int(new) == 2:
            figure_row.children = [figures[0], figures[1]]
        elif int(new) == 3:
            figure_row.children = [figures[0], figures[1], figures[2]]

    figure_drop.on_change("value", on_change)

    color_mapper = bokeh.models.LinearColorMapper(
        low=0, high=1, palette=bokeh.palettes.Plasma[256])
    for figure in figures:
        colorbar = bokeh.models.ColorBar(color_mapper=color_mapper,
                                         orientation="horizontal",
                                         background_fill_alpha=0.,
                                         location="bottom_center",
                                         major_tick_line_color="black",
                                         bar_line_color="black")
        figure.add_layout(colorbar, 'center')

    # Database/File system loader(s)
    for group in config.file_groups:
        if group.label not in data.LOADERS:
            if group.locator == "database":
                loader = load.Loader.group_args(group, args, database=database)
            else:
                loader = load.Loader.group_args(group, args)
            data.add_loader(group.label, loader)

    renderers = {}
    viewers = {}
    for name, loader in data.LOADERS.items():
        if isinstance(loader, rdt.Loader):
            viewer = rdt.View(loader)
        elif isinstance(loader, earth_networks.Loader):
            viewer = earth_networks.View(loader)
        elif isinstance(loader, data.GPM):
            viewer = view.GPMView(loader, color_mapper)
        elif isinstance(loader, satellite.EIDA50):
            viewer = view.EIDA50(loader, color_mapper)
        elif isinstance(loader, intake_loader.IntakeLoader):
            viewer = view.UMView(loader, color_mapper)
            viewer.set_hover_properties(intake_loader.INTAKE_TOOLTIPS,
                                        intake_loader.INTAKE_FORMATTERS)
        else:
            viewer = view.UMView(loader, color_mapper)
        viewers[name] = viewer
        renderers[name] = [viewer.add_figure(f) for f in figures]

    artist = Artist(viewers, renderers)
    renderers = []
    for _, r in artist.renderers.items():
        renderers += r

    image_sources = []
    for name, viewer in artist.viewers.items():
        if isinstance(viewer, (view.UMView, view.GPMView, view.EIDA50)):
            image_sources.append(viewer.source)

    # Lakes
    for figure in figures:
        add_feature(figure, data.LAKES, color="lightblue")

    features = []
    for figure in figures:
        features += [
            add_feature(figure, data.COASTLINES),
            add_feature(figure, data.BORDERS)
        ]

    # Disputed borders
    for figure in figures:
        add_feature(figure, data.DISPUTED, color="red")

    toggle = bokeh.models.CheckboxButtonGroup(labels=["Coastlines"],
                                              active=[0],
                                              width=135)

    def on_change(attr, old, new):
        if len(new) == 1:
            for feature in features:
                feature.visible = True
        else:
            for feature in features:
                feature.visible = False

    toggle.on_change("active", on_change)

    dropdown = bokeh.models.Dropdown(label="Color",
                                     menu=[("Black", "black"),
                                           ("White", "white")],
                                     width=50)
    autolabel(dropdown)

    def on_change(attr, old, new):
        for feature in features:
            feature.glyph.line_color = new

    dropdown.on_change("value", on_change)

    slider = bokeh.models.Slider(start=0,
                                 end=1,
                                 step=0.1,
                                 value=1.0,
                                 show_value=False)

    def is_image(renderer):
        return isinstance(getattr(renderer, 'glyph', None), bokeh.models.Image)

    image_renderers = [r for r in renderers if is_image(r)]
    custom_js = bokeh.models.CustomJS(args=dict(renderers=image_renderers),
                                      code="""
            renderers.forEach(function (r) {
                r.glyph.global_alpha = cb_obj.value
            })
            """)
    slider.js_on_change("value", custom_js)

    menu = []
    for k, _ in config.patterns:
        menu.append((k, k))

    image_controls = images.Controls(menu)

    def on_change(attr, old, new):
        if int(new) == 1:
            image_controls.labels = ["Show"]
        elif int(new) == 2:
            image_controls.labels = ["L", "R"]
        elif int(new) == 3:
            image_controls.labels = ["L", "C", "R"]

    figure_drop.on_change("value", on_change)

    image_controls.subscribe(artist.on_visible)

    div = bokeh.models.Div(text="", width=10)
    border_row = bokeh.layouts.row(bokeh.layouts.column(toggle),
                                   bokeh.layouts.column(div),
                                   bokeh.layouts.column(dropdown))

    # Pre-select first layer
    for name, _ in config.patterns:
        image_controls.select(name)
        break

    navigator = navigate.Navigator(config, database)

    # Pre-select menu choices (if any)
    initial_state = {}
    for _, pattern in config.patterns:
        initial_state = db.initial_state(navigator, pattern=pattern)
        break

    middlewares = [
        db.Log(verbose=True), keys.navigate,
        db.InverseCoordinate("pressure"), db.next_previous,
        db.Controls(navigator),
        db.Converter({
            "valid_times": db.stamps,
            "inital_times": db.stamps
        }), colors.palettes
    ]
    store = redux.Store(redux.combine_reducers(db.reducer, series.reducer,
                                               colors.reducer),
                        initial_state=initial_state,
                        middlewares=middlewares)

    # Connect color palette controls
    color_palette = colors.ColorPalette(color_mapper)
    color_palette.connect(store)
    names = list(sorted(bokeh.palettes.all_palettes.keys()))
    store.dispatch(colors.set_palette_name("Viridis"))
    store.dispatch(colors.set_palette_number(256))
    store.dispatch(colors.set_palette_names(names))

    # Connect limit controllers to store
    source_limits = colors.SourceLimits(image_sources)
    source_limits.subscribe(store.dispatch)

    user_limits = colors.UserLimits()
    user_limits.connect(store)

    # Connect navigation controls
    controls = db.ControlView()
    controls.subscribe(store.dispatch)
    store.subscribe(controls.render)

    def old_world(state):
        kwargs = {k: state.get(k, None) for k in db.State._fields}
        return db.State(**kwargs)

    old_states = (rx.Stream().listen_to(store).map(old_world).distinct())
    old_states.subscribe(artist.on_state)

    # Set top-level navigation
    store.dispatch(db.set_value("patterns", config.patterns))

    tabs = bokeh.models.Tabs(tabs=[
        bokeh.models.Panel(child=bokeh.layouts.column(
            bokeh.models.Div(text="Navigate:"), controls.layout,
            bokeh.models.Div(text="Compare:"), bokeh.layouts.row(figure_drop),
            image_controls.column),
                           title="Control"),
        bokeh.models.Panel(child=bokeh.layouts.column(
            border_row, bokeh.layouts.row(slider), color_palette.layout,
            user_limits.layout),
                           title="Settings")
    ])

    # Series sub-figure widget
    series_figure = bokeh.plotting.figure(plot_width=400,
                                          plot_height=200,
                                          x_axis_type="datetime",
                                          toolbar_location=None,
                                          border_fill_alpha=0)
    series_figure.toolbar.logo = None
    series_row = bokeh.layouts.row(series_figure, name="series")

    def place_marker(figure, source):
        figure.circle(x="x", y="y", color="red", source=source)

        def cb(event):
            source.data = {"x": [event.x], "y": [event.y]}

        return cb

    marker_source = bokeh.models.ColumnDataSource({"x": [], "y": []})
    series_view = series.SeriesView.from_groups(series_figure,
                                                config.file_groups,
                                                directory=args.directory)
    series_view.subscribe(store.dispatch)
    series_args = (rx.Stream().listen_to(store).map(
        series.select_args).filter(lambda x: x is not None).distinct())
    series_args.map(lambda a: series_view.render(*a))
    series_args.map(print)  # Note: map(print) creates None stream
    for f in figures:
        f.on_event(bokeh.events.Tap, series_view.on_tap)
        f.on_event(bokeh.events.Tap, place_marker(f, marker_source))

    # Minimise controls to ease navigation
    compact_button = bokeh.models.Button(label="Compact")
    compact_minus = bokeh.models.Button(label="-", width=50)
    compact_plus = bokeh.models.Button(label="+", width=50)
    compact_navigation = bokeh.layouts.column(
        compact_button,
        bokeh.layouts.row(compact_minus, compact_plus, width=100))
    control_root = bokeh.layouts.column(compact_button, tabs, name="controls")

    display = "large"

    def on_compact():
        nonlocal display
        if display == "large":
            control_root.height = 100
            control_root.width = 120
            compact_button.width = 100
            compact_button.label = "Expand"
            control_root.children = [compact_navigation]
            display = "compact"
        else:
            control_root.height = 500
            control_root.width = 300
            compact_button.width = 300
            compact_button.label = "Compact"
            control_root.children = [compact_button, tabs]
            display = "large"

    compact_button.on_click(on_compact)

    # Add key press support
    key_press = keys.KeyPress()
    key_press.subscribe(store.dispatch)

    document = bokeh.plotting.curdoc()
    document.title = "FOREST"
    document.add_root(control_root)
    document.add_root(series_row)
    document.add_root(figure_row)
    document.add_root(key_press.hidden_button)
Ejemplo n.º 14
0
def main(argv=None):

    args = parse_args.parse_args(argv)
    data.AUTO_SHUTDOWN = args.auto_shutdown

    if len(args.files) > 0:
        if args.config_file is not None:
            raise Exception(
                '--config-file and [FILE [FILE ...]] not compatible')
        config = cfg.from_files(args.files, args.file_type)
    else:
        config = cfg.Config.load(args.config_file,
                                 variables=cfg.combine_variables(
                                     os.environ, args.variables))

    # Feature toggles
    if "feature" in config.plugins:
        features = plugin.call(config.plugins["feature"].entry_point)
    else:
        features = config.features
    data.FEATURE_FLAGS = features

    # Full screen map
    viewport = config.default_viewport
    x_range, y_range = geo.web_mercator(viewport.lon_range, viewport.lat_range)
    figure = bokeh.plotting.figure(
        x_range=x_range,
        y_range=y_range,
        #output_backend = "svg",
        x_axis_type="mercator",
        y_axis_type="mercator",
        active_scroll="wheel_zoom")

    figures = [figure]
    for _ in range(2):
        f = bokeh.plotting.figure(x_range=figure.x_range,
                                  y_range=figure.y_range,
                                  x_axis_type="mercator",
                                  y_axis_type="mercator",
                                  active_scroll="wheel_zoom")
        figures.append(f)

    for f in figures:
        f.axis.visible = False
        f.toolbar.logo = None
        f.toolbar_location = None
        f.min_border = 0

    figure_row = layers.FigureRow(figures)

    color_mapper = bokeh.models.LinearColorMapper(
        low=0, high=1, palette=bokeh.palettes.Plasma[256])

    # Convert config to datasets
    datasets = {}
    datasets_by_pattern = {}
    label_to_pattern = {}
    for group in config.file_groups:
        settings = {
            "label": group.label,
            "pattern": group.pattern,
            "locator": group.locator,
            "database_path": group.database_path,
            "directory": group.directory
        }
        dataset = drivers.get_dataset(group.file_type, settings)
        datasets[group.label] = dataset
        datasets_by_pattern[group.pattern] = dataset
        label_to_pattern[group.label] = group.pattern

    # Lakes
    for figure in figures:
        add_feature(figure, data.LAKES, color="lightblue")

    features = []
    for figure in figures:
        render2 = add_feature(figure, data.LAKES, color="lightblue")
        features += [
            add_feature(figure, data.COASTLINES),
            add_feature(figure, data.BORDERS)
        ]

    # Disputed borders
    for figure in figures:
        add_feature(figure, data.DISPUTED, color="red")

    toggle = bokeh.models.CheckboxGroup(labels=["Coastlines"],
                                        active=[0],
                                        width=135)

    def on_change(attr, old, new):
        if len(new) == 1:
            for feature in features:
                feature.visible = True
        else:
            for feature in features:
                feature.visible = False

    toggle.on_change("active", on_change)

    dropdown = bokeh.models.Dropdown(label="Color",
                                     menu=[("Black", "black"),
                                           ("White", "white")],
                                     width=50)
    autolabel(dropdown)

    def on_change(event):
        for feature in features:
            feature.glyph.line_color = new

    dropdown.on_click(on_change)

    layers_ui = layers.LayersUI()

    div = bokeh.models.Div(text="", width=10)
    border_row = bokeh.layouts.row(bokeh.layouts.column(toggle),
                                   bokeh.layouts.column(div),
                                   bokeh.layouts.column(dropdown))

    # Add optional sub-navigators
    sub_navigators = {
        key: dataset.navigator()
        for key, dataset in datasets_by_pattern.items()
        if hasattr(dataset, "navigator")
    }
    navigator = navigate.Navigator(sub_navigators)

    # Pre-select menu choices (if any)
    initial_state = {}
    for pattern, _ in sub_navigators.items():
        initial_state = db.initial_state(navigator, pattern=pattern)
        break

    middlewares = [
        keys.navigate,
        db.InverseCoordinate("pressure"),
        db.next_previous,
        db.Controls(navigator),  # TODO: Deprecate this middleware
        colors.palettes,
        colors.middleware(),
        presets.Middleware(presets.proxy_storage(config.presets_file)),
        presets.middleware,
        layers.middleware,
        navigator,
        mws.echo,
    ]
    store = redux.Store(forest.reducer,
                        initial_state=initial_state,
                        middlewares=middlewares)

    # Colorbar user interface
    colorbar_ui = forest.components.ColorbarUI()
    colorbar_ui.connect(store)

    # Add time user interface
    time_ui = forest.components.TimeUI()
    time_ui.connect(store)

    # Connect MapView orchestration to store
    opacity_slider = forest.layers.OpacitySlider()
    source_limits = colors.SourceLimits().connect(store)
    factory_class = forest.layers.factory(color_mapper, figures, source_limits,
                                          opacity_slider)
    gallery = forest.layers.Gallery.from_datasets(datasets, factory_class)
    gallery.connect(store)

    # Connect layers controls
    layers_ui.add_subscriber(store.dispatch)
    layers_ui.connect(store)

    # Connect tools controls

    display_names = {
        "time_series": "Display Time Series",
        "profile": "Display Profile",
        "barc": "BARC Toolkit"
    }
    available_features = {
        k: display_names[k]
        for k in display_names.keys() if data.FEATURE_FLAGS[k]
    }

    tools_panel = tools.ToolsPanel(available_features)
    tools_panel.connect(store)

    #barc_toolbar=bokeh.models.tools.Toolbar(tools=barc_tools,logo=None)
    if data.FEATURE_FLAGS["BARC"]:
        barc = BARC(figures)
        tools_panel.layout.children.extend(barc.ToolBar())

    # Navbar components
    navbar = Navbar(show_diagram_button=len(available_features) > 0)
    navbar.connect(store)

    # Connect tap listener
    tap_listener = screen.TapListener()
    tap_listener.connect(store)

    # Connect figure controls/views
    figure_ui = layers.FigureUI()
    figure_ui.add_subscriber(store.dispatch)
    figure_row.connect(store)

    # Tiling picker
    if config.use_web_map_tiles:
        tile_picker = forest.components.TilePicker()
        for figure in figures:
            tile_picker.add_figure(figure)
        tile_picker.connect(store)

    # Connect color palette controls
    colors.ColorMapperView(color_mapper).connect(store)
    color_palette = colors.ColorPalette().connect(store)

    # Connect limit controllers to store
    user_limits = colors.UserLimits().connect(store)

    # Preset
    preset_ui = presets.PresetUI().connect(store)

    # Connect navigation controls
    controls = db.ControlView()
    controls.connect(store)

    # Add support for a modal dialogue
    if data.FEATURE_FLAGS["multiple_colorbars"]:
        view = forest.components.modal.Tabbed()
    else:
        view = forest.components.modal.Default()
    modal = forest.components.Modal(view=view)
    modal.connect(store)

    # Set default time series visibility
    store.dispatch(tools.on_toggle_tool("time_series", False))

    # Set default profile visibility
    store.dispatch(tools.on_toggle_tool("profile", False))

    # Set top-level navigation
    store.dispatch(db.set_value("patterns", config.patterns))

    # Pre-select first map_view layer
    for label, dataset in datasets.items():
        pattern = label_to_pattern[label]
        for variable in navigator.variables(pattern):
            spec = {
                "label": label,
                "dataset": label,
                "variable": variable,
                "active": [0]
            }
            store.dispatch(forest.layers.save_layer(0, spec))
            break
        break

    # Set variable dimensions (needed by modal dialogue)
    for label, dataset in datasets.items():
        pattern = label_to_pattern[label]
        values = navigator.variables(pattern)
        store.dispatch(dimension.set_variables(label, values))

    # Select web map tiling
    if config.use_web_map_tiles:
        store.dispatch(tiles.set_tile(tiles.STAMEN_TERRAIN))
        store.dispatch(tiles.set_label_visible(True))

    # Organise controls/settings
    layouts = {}
    layouts["controls"] = [
        bokeh.models.Div(text="Layout:"), figure_ui.layout,
        bokeh.models.Div(text="Navigate:"), controls.layout,
        bokeh.models.Div(text="Compare:"), layers_ui.layout
    ]
    layouts["settings"] = [
        border_row,
        opacity_slider.layout,
        preset_ui.layout,
        color_palette.layout,
        user_limits.layout,
        bokeh.models.Div(text="Tiles:"),
    ]
    if config.use_web_map_tiles:
        layouts["settings"].append(tile_picker.layout)

    tabs = bokeh.models.Tabs(tabs=[
        bokeh.models.Panel(child=bokeh.layouts.column(*layouts["controls"]),
                           title="Control"),
        bokeh.models.Panel(child=bokeh.layouts.column(*layouts["settings"]),
                           title="Settings")
    ])

    tool_figures = {}
    if data.FEATURE_FLAGS["time_series"]:
        # Series sub-figure widget
        series_figure = bokeh.plotting.figure(plot_width=400,
                                              plot_height=200,
                                              x_axis_type="datetime",
                                              toolbar_location=None,
                                              border_fill_alpha=0)
        series_figure.toolbar.logo = None

        series_view = series.SeriesView.from_groups(series_figure,
                                                    config.file_groups)
        series_view.add_subscriber(store.dispatch)
        series_args = (rx.Stream().listen_to(store).map(
            series.select_args).filter(lambda x: x is not None).distinct())
        series_args.map(lambda a: series_view.render(*a))
        series_args.map(print)  # Note: map(print) creates None stream

        tool_figures["series_figure"] = series_figure

    if data.FEATURE_FLAGS["profile"]:
        # Profile sub-figure widget
        profile_figure = bokeh.plotting.figure(plot_width=300,
                                               plot_height=450,
                                               toolbar_location=None,
                                               border_fill_alpha=0)
        profile_figure.toolbar.logo = None
        profile_figure.y_range.flipped = True

        profile_view = profile.ProfileView.from_groups(profile_figure,
                                                       config.file_groups)
        profile_view.add_subscriber(store.dispatch)
        profile_args = (rx.Stream().listen_to(store).map(
            profile.select_args).filter(lambda x: x is not None).distinct())
        profile_args.map(lambda a: profile_view.render(*a))
        profile_args.map(print)  # Note: map(print) creates None stream

        tool_figures["profile_figure"] = profile_figure

    tool_layout = tools.ToolLayout(**tool_figures)
    tool_layout.connect(store)

    for f in figures:
        f.on_event(bokeh.events.Tap, tap_listener.update_xy)
        marker = screen.MarkDraw(f).connect(store)

    control_root = bokeh.layouts.column(tabs, name="controls")

    # Add key press support
    key_press = keys.KeyPress()
    key_press.add_subscriber(store.dispatch)

    document = bokeh.plotting.curdoc()
    document.title = "FOREST"
    document.add_root(control_root)
    document.add_root(
        bokeh.layouts.column(tools_panel.layout,
                             tool_layout.layout,
                             width=400,
                             name="series"))
    document.add_root(bokeh.layouts.row(time_ui.layout, name="time"))
    for root in navbar.roots:
        document.add_root(root)
    document.add_root(colorbar_ui.layout)
    document.add_root(figure_row.layout)
    document.add_root(key_press.hidden_button)
    document.add_root(modal.layout)
Ejemplo n.º 15
0
 def setUp(self):
     self.stream = rx.Stream()
     self.subscriber = unittest.mock.Mock()