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