? users online
  • Logout
    • Open hangout
    • Open chat for current file
<div class="notebook">

<div class="nb-cell markdown">
# Displaying results as charts with Vega-Lite

Numerical results are often easier to understand when rendered as a chart than a list of numbers. The SWISH web interface for SWI-Prolog allows rendering data through [Vega-Lite](https://vega.github.io/vega-lite/). Vega-Lite is a high-level grammar of interactive graphics. It provides a concise JSON syntax for rapidly generating visualizations to support analysis.

## How it works

Creating a Vega-Lite chart requires including the directive `:- use_rendering(vega).` and binding a Prolog variable to a _dict_ with the _tag_ `vega` and the data needed by Vega-Lite. The structure of the dict directly mirrors the Vega-Lite JSON specification.

If the specification is incorrect, Vega-Lite may fail to render the chart. In this case, SWISH will display an error message along with the raw Prolog term.
</div>

<div class="nb-cell markdown">
## From JSON to a Prolog dict using quasi-quotation

The SWISH Vega-Lite renderer expects a SWI-Prolog _dict_ with the tag `vega`. This structure is designed to map directly to the Vega-Lite JSON specification. While you can create these dicts using Prolog's native syntax, it can be more convenient to use the original JSON, especially for complex charts.

SWI-Prolog's **quasi-quotation** feature lets you embed JSON directly into your code. The syntax is ``{|json|| ... |}``. The content is parsed at compile time into a Prolog dict, which much more convenient than parsing a string at runtime.

Because JSON objects don't have tags, the resulting Prolog dict will have a variable tag. We can use `is_dict(Dict, vega)` to unify this variable with the required `vega` tag.

Quasi-quotations also support interpolating Prolog terms inside the JSON. A variable in the JSON is replaced by the value of the Prolog variable with the same name. This is useful for dynamically generating parts of the chart specification, such as the data.
</div>

<div class="nb-cell program">
:- use_rendering(vega).
:- use_module(library(http/json)).

bar_chart(Chart) :-
    Chart = {|json||
        {
            "description": "A simple bar chart with embedded data.",
            "data": {
                "values": [
                    {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
                    {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
                    {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
                ]
            },
            "mark": "bar",
            "encoding": {
                "x": {"field": "a", "type": "ordinal"},
                "y": {"field": "b", "type": "quantitative"},
                "tooltip": {
                    "field": "b",
                    "type": "quantitative",
                    "aggregate": "max"
                }
            }
        }
    |},
    is_dict(Chart, vega).
</div>

<div class="nb-cell query">
bar_chart(Chart).
</div>

<div class="nb-cell markdown">
### Using native SWI-Prolog dicts

Alternatively, you can construct the chart specification using native SWI-Prolog dict syntax. This avoids the need for quasi-quotation and `library(http/json)`. The structure is the same, but uses Prolog's syntax for keys, atoms, and strings.
</div>

<div class="nb-cell program">
:- use_rendering(vega).

bar_chart_dict(Chart) :-
    Chart = vega{
        description: "A simple bar chart with embedded data.",
        data: _{
            values: [
                _{a: 'A', b: 28}, _{a: 'B', b: 55}, _{a: 'C', b: 43},
                _{a: 'D', b: 91}, _{a: 'E', b: 81}, _{a: 'F', b: 53},
                _{a: 'G', b: 19}, _{a: 'H', b: 87}, _{a: 'I', b: 52}
            ]
        },
        mark: bar,
        encoding: _{
            x: _{field: a, type: ordinal},
            y: _{field: b, type: quantitative},
            tooltip: _{
                field: b,
                type: quantitative,
                aggregate: max
            }
        }
    }.
</div>

<div class="nb-cell query">
bar_chart_dict(Chart).
</div>

<div class="nb-cell markdown">
## A scatter plot

This example creates a scatter plot from data generated by a Prolog predicate. We use `findall/3` to collect the data points as a list of dicts, which is the format Vega-Lite expects for its `values` field.
</div>

<div class="nb-cell program">
:- use_rendering(vega).
:- use_module(library(http/json)).

point(I, _{x:X, y:Y, category:C}) :-
    between(0, 50, I),
    X is cos(I*pi/25) + random_float*0.2,
    Y is sin(I*pi/25) + random_float*0.2,
    ( I < 25 -> C = 'A' ; C = 'B' ).

scatter_plot(Chart) :-
    findall(Point, point(_, Point), Points),
    Chart = {|json(Points)||
        {
            "description": "A scatterplot.",
            "data": { "values": Points },
            "mark": "point",
            "encoding": {
                "x": {"field": "x", "type": "quantitative"},
                "y": {"field": "y", "type": "quantitative"},
                "color": {"field": "category", "type": "nominal"}
            }
        }
    |},
    is_dict(Chart, vega).
</div>

<div class="nb-cell query">
scatter_plot(Chart).
</div>

<div class="nb-cell markdown" name="md5">
## Embedding in an HTML cell

It is also possible to create a Vega-Lite chart directly in an HTML cell.

The example below creates a `&lt;div&gt;` element with `id="vis"` as a placeholder for the chart. The `&lt;script&gt;` tag then calls `vegaEmbed()` to render the chart. Note the use of `notebook.$("#vis")` to select the placeholder; the `notebook` object provides a jQuery instance scoped to the current cell, which prevents ID conflicts with other cells in the notebook.
</div>

<div class="nb-cell html" name="htm1">
<div id="vis"></div>
<script>
  require(["vega-embed"], function(vegaEmbed) {
    var yourVlSpec = {
        description: 'A simple bar chart with embedded data.',
        data: {
          values: [
            {a: 'A', b: 28},
            {a: 'B', b: 55},
            {a: 'C', b: 43},
            {a: 'D', b: 91},
            {a: 'E', b: 81},
            {a: 'F', b: 53},
            {a: 'G', b: 19},
            {a: 'H', b: 87},
            {a: 'I', b: 52},
          ],
        },
        mark: 'bar',
        encoding: {
          x: {field: 'a', type: 'ordinal'},
          y: {field: 'b', type: 'quantitative'},
        },
      };
      vegaEmbed(notebook.$("#vis")[0], yourVlSpec);
    });
</script>
</div>

<div class="nb-cell markdown" name="md6">
## Further reading

Vega-Lite provides a rich set of visualizations and interactions. Please visit the [Vega-Lite examples gallery](https://vega.github.io/vega-lite/examples/) and [documentation](https://vega.github.io/vega-lite/docs/) for details and inspiration.
</div>

</div>