Exemplo n.º 1
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()
Exemplo n.º 2
0
    """Configure borders, coastlines and lakes"""
    if isinstance(action, dict):
        try:
            action = actions.Action.from_dict(action)
        except TypeError:
            # TODO: Support Action throughout codebase
            return state
    # Reduce state.borders
    if isinstance(state, dict):
        state = forest.state.State.from_dict(state)
    if action.kind == actions.SET_BORDERS_VISIBLE:
        state.borders.visible = action.payload
    elif action.kind == actions.SET_BORDERS_LINE_COLOR:
        state.borders.line_color = action.payload
    return state.to_dict()


reducer = redux.combine_reducers(
            db.reducer,
            layers.reducer,
            screen.reducer,
            tools.reducer,
            colors.reducer,
            colors.limits_reducer,
            presets.reducer,
            tiles.reducer,
            dimension.reducer,
            html_ready.reducer,
            state_reducer,
            borders_reducer)
Exemplo n.º 3
0
def test_combine_reducers(actions, expect):
    reducer = redux.combine_reducers(series.reducer, db.reducer)
    state = {}
    for action in actions:
        state = reducer(state, action)
    assert state == expect
Exemplo n.º 4
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)
Exemplo n.º 5
0
def test_combine_reducers(actions, expect):
    reducer = redux.combine_reducers(screen.reducer, forest.db.control.reducer)
    state = {}
    for action in actions:
        state = reducer(state, action)
    assert state == expect
Exemplo n.º 6
0
"""Reducer"""
from forest import (
    redux,
    db,
    layers,
    screen,
    tools,
    colors,
    presets,
    dimension)
from forest.components import (
    tiles)


reducer = redux.combine_reducers(
            db.reducer,
            layers.reducer,
            screen.reducer,
            tools.reducer,
            colors.reducer,
            colors.limits_reducer,
            presets.reducer,
            tiles.reducer,
            dimension.reducer)
Exemplo n.º 7
0
def test_reducer_update():
    reducer = redux.combine_reducers(presets.reducer, colors.reducer)
    state = {}
    for action in [presets.save_preset("A"), presets.save_preset("A")]:
        state = reducer(state, action)
    assert set(state["presets"]["labels"].values()) == {"A"}