예제 #1
0
 def test_navigator_api(self):
     path = "file.nc"
     variable = "air_temperature"
     initial_time = "2019-01-01 00:00:00"
     valid_time = "2019-01-01 12:00:00"
     pressure = 750.
     navigator = db.Navigator()
     controls = db.Controls(navigator)
     store = redux.Store(db.reducer, middlewares=[controls])
     actions = [
         db.set_value("pattern", path),
         db.set_value("variable", variable),
         db.set_value("initial_time", initial_time),
         db.set_value("valid_time", valid_time)
     ]
     for action in actions:
         store.dispatch(action)
     result = store.state
     expect = {
         "pattern": path,
         "variable": variable,
         "variables": [variable],
         "initial_time": initial_time,
         "initial_times": [initial_time],
         "valid_time": valid_time,
         "valid_times": [valid_time],
         "pressures": [pressure],
     }
     self.assertEqual(expect, result)
예제 #2
0
 def test_set_valid_time_sets_pressures(self):
     path = "file.nc"
     variable = "air_temperature"
     initial_time = "2019-01-01 00:00:00"
     valid_time = "2019-01-01 12:00:00"
     pressure = 750.
     index = 0
     self.database.insert_file_name(path, initial_time)
     self.database.insert_time(path, variable, valid_time, index)
     self.database.insert_pressure(path, variable, pressure, index)
     store = redux.Store(db.reducer, middlewares=[self.controls])
     actions = [
         db.set_value("pattern", path),
         db.set_value("variable", variable),
         db.set_value("initial_time", initial_time),
         db.set_value("valid_time", valid_time)
     ]
     for action in actions:
         store.dispatch(action)
     result = store.state
     expect = {
         "pattern": path,
         "variable": variable,
         "variables": [variable],
         "initial_time": initial_time,
         "initial_times": [initial_time],
         "valid_time": valid_time,
         "valid_times": [valid_time],
         "pressures": [pressure],
     }
     self.assertEqual(expect, result)
예제 #3
0
    def test_set_variable_given_initial_time_changes_times_and_pressure(self):
        path = "some.nc"
        initial_time = "2019-01-01 00:00:00"
        self.database.insert_file_name(path, initial_time)
        index = 0
        for variable, valid_time, pressure in [
            ("air_temperature", "2019-01-01 01:00:00", 1000.),
            ("olr", "2019-01-01 01:03:00", 10.)
        ]:
            self.database.insert_time(path, variable, valid_time, index)
            self.database.insert_pressure(path, variable, pressure, index)

        store = redux.Store(db.reducer, middlewares=[self.controls])
        actions = [
            db.set_value("pattern", path),
            db.set_value("variable", "air_temperature"),
            db.set_value("initial_time", initial_time),
            db.set_value("variable", "olr")
        ]
        for action in actions:
            store.dispatch(action)

        result = store.state
        expect = {
            "pattern": path,
            "variable": "olr",
            "variables": ["air_temperature", "olr"],
            "initial_time": initial_time,
            "initial_times": [initial_time],
            "valid_times": ["2019-01-01 01:03:00"],
            "pressures": [0.0]
        }
        self.assertEqual(expect, result)
예제 #4
0
 def test_set_value_action_creator(self):
     result = db.set_value("K", "V")
     expect = {
         "kind": "SET_VALUE",
         "payload": dict(key="K", value="V")
     }
     self.assertEqual(expect, result)
예제 #5
0
 def test_on_variable_emits_state(self):
     value = "token"
     listener = unittest.mock.Mock()
     view = db.ControlView()
     view.subscribe(listener)
     view.on_change("variable")(None, None, value)
     listener.assert_called_once_with(db.set_value("variable", value))
예제 #6
0
 def test_reducer_given_set_value_action_adds_key_value(self):
     action = db.set_value("name", "value")
     state = {"previous": "constant"}
     result = db.reducer(state, action)
     expect = {
         "previous": "constant",
         "name": "value"
     }
     self.assertEqual(expect, result)
예제 #7
0
 def test_middleware_converts_next_value_to_set_value(self):
     store = redux.Store(db.reducer,
                         initial_state={
                             "k": 2,
                             "ks": [1, 2, 3]
                         })
     action = db.next_value("k", "ks")
     result = list(db.next_previous(store, action))
     self.assertEqual(result, [db.set_value("k", 3)])
예제 #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(db.reducer)
     old_states = (
         rx.Stream().listen_to(store).map(lambda x: db.State(**x)))
     old_states.subscribe(listener)
     store.dispatch(db.set_value("pressure", 1000))
     expect = db.State(pressure=1000)
     listener.assert_called_once_with(expect)
예제 #9
0
def test_type_system_middleware():
    times = np.array([dt.datetime(2019, 1, 1),
                      dt.datetime(2019, 1, 2)],
                     dtype="datetime64[s]")
    converter = db.Converter({"valid_times": db.stamps})
    store = redux.Store(db.reducer, middlewares=[converter])
    store.dispatch(db.set_value("valid_times", times))
    result = store.state
    expect = {"valid_times": ["2019-01-01 00:00:00", "2019-01-02 00:00:00"]}
    assert expect == result
예제 #10
0
 def test_middleware_converts_next_value_to_set_value(self):
     log = db.Log()
     state = {"k": 2, "ks": [1, 2, 3]}
     store = redux.Store(db.reducer,
                         initial_state=state,
                         middlewares=[db.next_previous, log])
     store.dispatch(db.next_value("k", "ks"))
     result = store.state
     expect = {"k": 3, "ks": [1, 2, 3]}
     self.assertEqual(expect, result)
     self.assertEqual(log.actions, [db.set_value("k", 3)])
예제 #11
0
def test_render_called_once_with_non_relevant_settings():
    """Render should only happen when relevant state changes"""
    store = redux.Store(redux.combine_reducers(db.reducer, colors.reducer))
    controls = colors.ColorPalette()
    controls.render = unittest.mock.Mock()
    controls.connect(store)
    for action in [
            colors.set_palette_name("Accent"),
            db.set_value("variable", "air_temperature")
    ]:
        store.dispatch(action)
    controls.render.assert_called_once()
예제 #12
0
 def test_set_pattern(self):
     initial = dt.datetime(2019, 1, 1)
     valid = dt.datetime(2019, 1, 1, 3)
     self.database.insert_file_name("file.nc", initial)
     self.database.insert_time("file.nc", "variable", valid, 0)
     action = db.set_value("pattern", "file.nc")
     self.store.dispatch(action)
     result = self.store.state
     expect = {
         "pattern": "file.nc",
         "variables": ["variable"],
         "initial_times": [str(initial)]
     }
     self.assertEqual(expect, result)
예제 #13
0
 def test_set_initial_time(self):
     initial = dt.datetime(2019, 1, 1)
     valid = dt.datetime(2019, 1, 1, 3)
     self.database.insert_file_name("file.nc", initial)
     self.database.insert_time("file.nc", "variable", valid, 0)
     action = db.set_value("initial_time", "2019-01-01 00:00:00")
     initial_state = {"pattern": "file.nc", "variable": "variable"}
     store = redux.Store(db.reducer,
                         initial_state=initial_state,
                         middlewares=[self.controls])
     store.dispatch(action)
     result = store.state
     expect = {
         "pattern": "file.nc",
         "variable": "variable",
         "initial_time": str(initial),
         "valid_times": [str(valid)]
     }
     self.assertEqual(expect, result)
예제 #14
0
def test_dimension_view_on_select():
    listener = unittest.mock.Mock()
    view = forest.db.control.DimensionView("item", "items")
    view.add_subscriber(listener)
    view.views["select"].on_select(None, None, "token")
    listener.assert_called_once_with(db.set_value("item", "token"))
예제 #15
0
파일: main.py 프로젝트: lecjlg/forest
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)
예제 #16
0
def test_reducer_immutable_state():
    state = {"pressure": 1000}
    next_state = db.reducer(state, db.set_value("pressure", 950))
    assert state["pressure"] == 1000
    assert next_state["pressure"] == 950
예제 #17
0
 def test_state_change_given_dropdown_message(self):
     action = db.set_value("pressure", "1000")
     self.store.dispatch(action)
     result = self.store.state
     expect = {"pressure": 1000.}
     self.assertEqual(expect, result)
예제 #18
0
파일: main.py 프로젝트: andrewgryan/forest
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)
예제 #19
0
from forest import screen, redux, rx, db

def test_position_reducer():
    state = screen.reducer({}, screen.set_position(0, 0))
    assert state == {"position": {"x": 0, "y": 0}}

def test_position_reducer_immutable_state():
    state = {"position": {"x": 1, "y": 1}}
    next_state = screen.reducer(state, screen.set_position(0, 0))
    assert state == {"position": {"x": 1, "y": 1}}
    assert next_state == {"position": {"x": 0, "y": 0}}

@pytest.mark.parametrize("actions,expect", [
    ([], {}),
    ([screen.set_position(0, 0)], {"position": {"x": 0, "y": 0}}),
    ([db.set_value("key", "value")], {"key": "value"}),
    ([
        screen.set_position(0, 0),
        db.set_value("key", "value")], {
            "key": "value",
            "position": {"x": 0, "y": 0}}),
])
def test_combine_reducers(actions, expect):
    reducer = redux.combine_reducers(screen.reducer, db.reducer)
    state = {}
    for action in actions:
        state = reducer(state, action)
    assert state == expect

def test_series_set_position_action():
    action = screen.set_position(0, 0)
예제 #20
0
def test_series_reducer_immutable_state():
    state = {"position": {"x": 1, "y": 1}}
    next_state = series.reducer(state, series.set_position(0, 0))
    assert state == {"position": {"x": 1, "y": 1}}
    assert next_state == {"position": {"x": 0, "y": 0}}


@pytest.mark.parametrize("actions,expect", [
    ([], {}),
    ([series.set_position(0, 0)], {
        "position": {
            "x": 0,
            "y": 0
        }
    }),
    ([db.set_value("key", "value")], {
        "key": "value"
    }),
    ([series.set_position(0, 0),
      db.set_value("key", "value")], {
          "key": "value",
          "position": {
              "x": 0,
              "y": 0
          }
      }),
])
def test_combine_reducers(actions, expect):
    reducer = redux.combine_reducers(series.reducer, db.reducer)
    state = {}
    for action in actions:
예제 #21
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.load_config(args.config_file)

    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)
        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)

    colors_controls = colors.Controls(color_mapper, "Plasma", 256)

    mapper_limits = MapperLimits(image_sources, color_mapper)

    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
        })
    ]
    store = redux.Store(db.reducer,
                        initial_state=initial_state,
                        middlewares=middlewares)
    controls = db.ControlView()
    controls.subscribe(store.dispatch)
    store.subscribe(controls.render)
    old_states = (db.Stream().listen_to(store).map(lambda x: db.State(**x)))
    old_states.subscribe(artist.on_state)

    # Ensure all listeners are pointing to the current state
    store.notify(store.state)
    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),
            colors_controls.layout,
            bokeh.layouts.row(mapper_limits.low_input),
            bokeh.layouts.row(mapper_limits.high_input),
            bokeh.layouts.row(mapper_limits.checkbox),
        ),
                           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 = Series.from_groups(series_figure,
                                config.file_groups,
                                directory=args.directory)
    old_states.subscribe(series.on_state)
    for f in figures:
        f.on_event(bokeh.events.Tap, series.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)
예제 #22
0
파일: main.py 프로젝트: cemac/forest-barc
def main(argv=None):
    config = configure(argv=argv)

    # 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, dataset in zip(config.file_groups, config.datasets):
        datasets[group.label] = dataset
        datasets_by_pattern[group.pattern] = dataset
        label_to_pattern[group.label] = group.pattern

    # print('\n\n\n\n', datasets, '\n\n\n\n')
    '''# 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()

    # 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.gallery.Gallery.map_view(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.append(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
    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)

    if not data.FEATURE_FLAGS["multiple_colorbars"]:
        # 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"] = [
        bokeh.models.Div(text="Borders, coastlines and lakes:"),
        border_ui.layout,
        opacity_slider.layout,
    ]
    if not data.FEATURE_FLAGS["multiple_colorbars"]:
        layouts["settings"].append(color_palette.layout)
        layouts["settings"].append(user_limits.layout)
    if config.defaults.presetui:
        layouts["settings"].append(preset_ui.layout)
    if config.use_web_map_tiles:
        layouts["settings"].append(bokeh.models.Div(text="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

        gallery = forest.gallery.Gallery.series_view(datasets, series_figure)
        gallery.connect(store)

        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

        gallery = forest.gallery.Gallery.profile_view(datasets, profile_figure)
        gallery.connect(store)

        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)