예제 #1
0
def test_validate_plot_color_by_missing_agg():
    """ Tests that the validate_plot function returns the correct value when
        there's an agg on X or Y but not COLOR BY.
    """
    svl_plot = {
        "type": "scatter",
        "x": {
            "field": "latitude"
        },
        "y": {
            "field": "temperature",
            "agg": "MAX"
        },
        "color_by": {
            "field": "humidity"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = ("If there's an aggregation on X or Y, "
                     "COLOR BY must also aggregate.")

    assert truth_ok == ok
    assert truth_message == message
예제 #2
0
def test_validate_plot_cannot_sort_on_both_axes():
    """ Tests that the validate_plot function returns the correct value when
        the plot has sort on X and Y.
    """
    svl_plot = {
        "type": "line",
        "data": "bigfoot",
        "x": {
            "field": "date",
            "temporal": "YEAR",
            "sort": "DESC"
        },
        "y": {
            "field": "classification",
            "agg": "COUNT",
            "sort": "ASC"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Cannot sort by two axes."

    assert truth_ok == ok
    assert truth_message == message
예제 #3
0
def test_validate_plot_split_by_and_color_by():
    """ Tests that the validate_plot function returns the correct value when
        the plot has a SPLIT BY and COLOR BY.
    """
    svl_plot = {
        "type": "line",
        "data": "bigfoot",
        "x": {
            "field": "date",
            "temporal": "YEAR"
        },
        "y": {
            "field": "date",
            "agg": "COUNT",
            "label": "Number of Sightings"
        },
        "split_by": {
            "field": "classification"
        },
        "color_by": {
            "field": "moon_phase",
            "agg": "MAX"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Cannot have COLOR BY and SPLIT BY on same plot."

    assert truth_ok == ok
    assert truth_message == message
예제 #4
0
def test_validate_plot_pie_must_have_axis():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a pie chart without an AXIS.
    """
    svl_plot = {"type": "pie", "data": "bigfoot", "hole": 0.3}

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Pie charts must have an axis."

    assert truth_ok == ok
    assert truth_message == message
예제 #5
0
def test_validate_plot_histogram_must_have_x_or_y():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a histogram without an X or Y.
    """
    svl_plot = {"type": "histogram", "data": "bigfoot", "bins": 10}

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Histograms must have an X or Y."

    assert truth_ok == ok
    assert truth_message == message
예제 #6
0
def test_validate_plot_pass():
    """ Tests that the validate_plot function returns the correct value when
        the plot passes.
    """
    svl_plot = {
        "type": "histogram",
        "data": "bigfoot",
        "y": {
            "field": "humidity"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = True
    truth_message = ""

    assert truth_ok == ok
    assert truth_message == message
예제 #7
0
def test_validate_plot_xy_must_have_x_and_y():
    """ Tests that the validate_plot function returns the correct value when
        the plot fails.
    """
    svl_plot = {
        "type": "bar",
        "data": "bigfoot",
        "x": {
            "field": "classification"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "XY plot does not have X and Y."

    assert truth_ok == ok
    assert truth_message == message
예제 #8
0
def test_validate_plot_histogram_and_pie_cannot_have_aggs():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a histogram or pie chart with an aggregation.
    """
    svl_plot = {
        "type": "pie",
        "data": "bigfoot",
        "axis": {
            "field": "classification",
            "agg": "COUNT"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Histograms and pie charts cannot have aggregations."

    assert truth_ok == ok
    assert truth_message == message
예제 #9
0
def test_validate_plot_bins_less_than_equal_to_zero():
    """ Tests that the validate_plot function returns the correct value when
        the plot has a BINS that's not greater than zero.
    """
    svl_plot = {
        "type": "histogram",
        "data": "bigfoot",
        "x": {
            "field": "wind_speed"
        },
        "bins": -4,
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "BINS must be greater than zero."

    assert truth_ok == ok
    assert truth_message == message
예제 #10
0
def test_validate_plot_step_less_than_equal_to_zero():
    """ Tests that the validate_plot function returns the correct value when
        the plot has a STEP that's not greater than zero.
    """
    svl_plot = {
        "type": "histogram",
        "data": "bigfoot",
        "x": {
            "field": "humidity"
        },
        "step": -1.3,
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "STEP must be greater than zero."

    assert truth_ok == ok
    assert truth_message == message
예제 #11
0
def test_validate_plot_hole_not_between_zero_and_one():
    """ Tests that the validate_plot function returns the correct value when
        the plot has a HOLE that's not between zero and one.
    """
    svl_plot = {
        "type": "pie",
        "data": "bigfoot",
        "axis": {
            "field": "has_location"
        },
        "hole": 1.2,
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "HOLE must be between zero and one."

    assert truth_ok == ok
    assert truth_message == message
예제 #12
0
def test_validate_plot_histogram_and_pie_cannot_have_temporals():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a histogram or pie chart with a temporal axis.
    """
    svl_plot = {
        "type": "histogram",
        "data": "bigfoot",
        "x": {
            "field": "date",
            "temporal": "YEAR"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Histograms and pie charts cannot have temporal axes."

    assert truth_ok == ok
    assert truth_message == message
예제 #13
0
def test_validate_plot_color_by_on_histogram_pie():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a histogram or pie chart with a COLOR BY axis.
    """
    svl_plot = {
        "type": "pie",
        "axis": {
            "field": "has_location"
        },
        "color_by": {
            "field": "temperature_mid"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Histograms and pie charts cannot have COLOR BY."

    assert truth_ok == ok
    assert truth_message == message
예제 #14
0
def test_validate_plot_histogram_cannot_have_step_and_bins():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a histogram that has a STEP and BINS declaration.
    """
    svl_plot = {
        "type": "histogram",
        "data": "bigfoot",
        "x": {
            "field": "wind_speed"
        },
        "step": 10,
        "bins": 100,
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Histogram cannot have STEP and BINS."

    assert truth_ok == ok
    assert truth_message == message
예제 #15
0
def test_validate_plot_histogram_cannot_have_x_and_y():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a histogram with x and y.
    """
    svl_plot = {
        "type": "histogram",
        "data": "bigfoot",
        "x": {
            "field": "humidity"
        },
        "y": {
            "field": "wind_speed"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Histograms can have X or Y, not both."

    assert truth_ok == ok
    assert truth_message == message
예제 #16
0
def test_validate_plot_split_by_on_pie():
    """ Tests that the validate_plot function returns the correct value when
        the plot is a pie chart with a SPLIT BY axis.
    """
    svl_plot = {
        "type": "pie",
        "data": "bigfoot",
        "axis": {
            "field": "has_location"
        },
        "split_by": {
            "field": "classification"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "Pie charts cannot have SPLIT BY."

    assert truth_ok == ok
    assert truth_message == message
예제 #17
0
def test_validate_plot_xy_cannot_have_aggs_on_x_and_y():
    """ Tests that the validate_Plot function returns the correct value when
        the plot is an XY plot that has an agg on X and Y.
    """
    svl_plot = {
        "type": "scatter",
        "data": "bigfoot",
        "x": {
            "field": "latitude",
            "agg": "MAX"
        },
        "y": {
            "field": "humidity",
            "agg": "MIN"
        },
    }

    ok, message = validate_plot(svl_plot)

    truth_ok = False
    truth_message = "XY plot cannot have an aggregation on X and Y."

    assert truth_ok == ok
    assert truth_message == message
예제 #18
0
def svl(svl_source,
        backend="plotly",
        datasets=[],
        offline_js=False,
        debug=False):
    """ Compiles the SVL source into a rendered plot template.

    Parameters
    -----------
    svl_source : str
        The SVL source code.
    backend : str
        Which plotting backend to render the plots with. Default: "plotly".
    datasets : list[str]
        A list of additional datasets to inject into the ast, for datasets not
        present in the SVL source code. Each dataset specifier must be of the
        form "dataset_name=dataset_location".
    offline_js : bool
        Whether to embed the javascript into the final HTML directly.
        Default: False.
    debug : bool
        If this flag is true, return the pretty-printed parse tree instead of
        the compiled program.

    Returns
    -------
    str
        The rendered HTML template for the plots.

    Raises
    ------
    ValueError
        If there is a malformed additional dataset specifier.
    SvlSyntaxError
        If there is a syntax error in the SVL source.
    SvlMissingFileError
        If there is a missing file in the SVL source or additional datasets.
    SvlMissingDatasetError
        If there is a dataset specified in a plot that isn't in the dataset
        specifiers for the SVL program.
    SvlPlotError
        If there is an error in any of the SVL plots.
    SvlDataLoadError
        If there is an error loading the data into sqlite.
    SvlDataProcessingError
        If there is an error processing the plot data.
    NotImplementedError
        If a backend is selected that hasn't been implemented.
    """
    if debug:
        return parse_svl(svl_source, debug=True).pretty()

    for dataset in datasets:
        if len(dataset.split("=")) != 2:
            raise ValueError(
                "dataset {} needs to be name=path".format(dataset))

    additional_datasets = _extract_additional_datasets(datasets)

    try:
        svl_ast = parse_svl(svl_source, **additional_datasets)
    except SvlSyntaxError as e:
        raise e

    # Validate that all of the files exist that need to exist.
    for _, dataset in svl_ast["datasets"].items():
        if ("file" in dataset) and (not os.path.exists(dataset["file"])):
            raise SvlMissingFileError("File {} does not exist.".format(
                dataset["file"]))

    # Flatten the AST plot representation into a list with grid coordinates.
    svl_plots = tree_to_grid(svl_ast)

    # Validate the plots.
    for plot in svl_plots:
        # Each plot must point to a dataset that exists.
        if plot["data"] not in svl_ast["datasets"]:
            existing_dataset = ", ".join(list(svl_ast["datasets"].keys()))
            raise SvlMissingDatasetError(
                "Dataset {} is not in provided datasets {}.".format(
                    plot["data"], existing_dataset))
        # Each plot must be a valid specification.
        ok, msg = validate_plot(plot)
        if not ok:
            raise SvlPlotError("Plot error: {}".format(msg))

    # Set up the connection to sqlite. Eventually this will be abstracted since
    # in principle we could have other data sources but for now sqlite is what
    # we've got.
    try:
        sqlite_conn = create_datasets(svl_ast["datasets"])
    except sqlite3.DatabaseError as e:
        raise SvlDataLoadError("Error loading data: {}.".format(e))

    # Get the data for the plots.
    try:
        svl_plot_data = [get_svl_data(plot, sqlite_conn) for plot in svl_plots]
    except sqlite3.DatabaseError as e:
        raise SvlDataProcessingError(
            "Error processing plot data: {}".format(e))

    # Select and render the template.
    if backend == "plotly":
        template_vars = plotly_template_vars(svl_plots, svl_plot_data)
        template = plotly_template()

        # If offline is selected, use the offline plotly in the template.
        template_vars["plotly_offline"] = offline_js
    else:
        raise NotImplementedError(
            "Unable to use {} as a backend.".format(backend))

    return template.render(**template_vars)