Create a Quine Icon Library with Python

thatDot avatar Michael Aglietti

Have you ever wanted to add flair to a graph visualization but are unsure which icons Quine supports? In this blog, we explore a Python script that fetches valid icon names from the web, configures the Exploration UI, then creates a graph of icon nodes for reference. The script uses several popular Python libraries, including Requests, BeautifulSoup, and Halo, along with the `/query-ui` and `/query/cypher` API endpoints.

Environment

Before we start, we need to ensure that we have the necessary libraries installed. We will be using `requests`, `beautifulsoup4`, `log_symbols`, and `halo`. You can install them using `pip`:

  • Quine
  • Python 3
  • Requests library (`pip install requests`)
  • BeautifulSoup library (`pip install beautifulsoup4`)
  • Optional Halo library for operation visuals  (`pip install log-symbols halo`)

Start Quine so that it is ready to run the script.

`java -jar quine-1.5.3.jar`

The Script

The script begins by importing the required libraries:

import requests
import json
from halo import Halo
from log_symbols import LogSymbols
from bs4 import BeautifulSoup

Build a list of icon names

We use the `requests` library to GET the webpage referenced in the Replace Node Appearances API documentation. Quine supports version 2.0.0 of the [Ionicons] icon set from the Ionic Framework. The link contains a list of 733 icons supported by Quine. A `try…except` block handles any errors that might occur during the request. If the request is successful, the script saves the HTML content of the page.

try:
    url = "https://ionic.io/ionicons/v2/cheatsheet.html"
    response = requests.get(url)
    html = response.content
    print(LogSymbols.SUCCESS.value, "GET Icon Cheatsheet")
except requests.exceptions.RequestException as e:
    raise SystemExit(e)

Next, we use BeautifulSoup to parse the HTML content of the page to extract all of the icon names. The `soup.select` method finds all `<input>` elements with a `name` attribute and returns a list, which are then looped over to extract the `value` attribute of each tag later. We output `len(all_icons)` to verify that we identified all of the icons.

soup = BeautifulSoup(html, 'html.parser')
all_icons = soup.select("input.name")
print(LogSymbols.SUCCESS.value, "Extract Icon Names:", len(all_icons) 

Create Node Appearances

Now that we have the icon names, we can use them to create node appearances for the Quine Exploration UI. We’ll use the `json` package to format the `nodeAppearances` data as JSON, and `requests` to replace the current `nodeAppearances` with a PUT to the `/query-ui/node-appearances` endpoint. We wrap the API call in `try…expect` as before to handle any errors.

  • `predicate`: filter which nodes to apply this style
  • `size`: the size of the icon in pixels
  • `icon`: the name of the icon
  • `label`: the label of the node

Note: Cypher does not allow dash (`-`) characters in node labels. We get around this by replacing all of the dashes with underscores in the node labels.

nodeAppearances = [
    {
        "predicate": {
            "propertyKeys": [],
            "knownValues": {},
            "dbLabel": icon_name["value"].replace("-", "_")
        },
        "size":40.0,
        "icon": icon_name["value"],
        "label": {
            "key": "name",
            "type": "Property"
        }
    } for icon_name in all_icons]
json_data = json.dumps(nodeAppearances)
try:
    headers = {'Content-type': 'application/json'}
    response = requests.put(
        'http://localhost:8080/api/v1/query-ui/node-appearances', data=json_data, headers=headers)
except requests.exceptions.RequestException as e:
    raise SystemExit(e)
print(LogSymbols.SUCCESS.value, "PUT Node Appearances")

Create Icon Nodes

Finally, our script creates icon nodes by sending a series of POST requests to the Quine `/query/cypher` endpoint. For each icon name, a Cypher query creates the corresponding icon node and connects it to the appropriate group node. We use `Halo` to create a spinner while we POST the icon data to Quine.


try:
    quineSpinner.start()
    for icon_name in all_icons:
        group = icon_name["value"].split('-',2)
        query_text = (
            f'MATCH (a), (b), (c) '
            f'WHERE id(a) = idFrom("{group[0]}") '
            f'  AND id(b) = idFrom("{group[1]}") '
            f'  AND id(c) = idFrom("{icon_name["value"]}") '
            f'SET a:{group[0]}, a.name = "{group[0]}" '
            f'SET b:{group[1]}, b.name = "{group[1]}" '
            f'SET c:{icon_name["value"].replace("-", "_")}, c.name = "{icon_name["value"]}" '
            f'CREATE (a)&lt;-[:GROUP]-(b)&lt;-[:GROUP]-(c)'
          ) if len(group) == 3 else (
            f'MATCH (a), (c) '
            f'WHERE id(a) = idFrom("{group[0]}") '
            f' AND id(c) = idFrom("{icon_name["value"]}") '
            f'SET a:{group[0]}, a.name = "{group[0]}" '
            f'SET c:{icon_name["value"].replace("-", "_")}, c.name = "{icon_name["value"]}" '
            f'CREATE (a)&lt;-[:GROUP]-(c)'
          )
        quineSpinner.text = query_text
        headers = {'Content-type': 'text/plain'}
        # print(query_text)
        response = requests.post(
            'http://localhost:8080/api/v1/query/cypher', data=query_text, headers=headers)
    quineSpinner.succeed('POST Icon Nodes')
except requests.exceptions.Timeout as timeout:
    quineSpinner.stop('Request Timeout: ' + timeout)
except requests.exceptions.RequestException as e:
    raise SystemExit(e)

Running the script

At this point, we are ready to run the script and visualize the icons supported in Quine.

`python3 iconLibrary.py`

The script updates the console as it moves through the blocks of code that we described above:

✔ GET Icon Cheatsheet
✔ Extract Icon Names: 733
✔ PUT Node Appearances
✔ POST Icon Nodes

Navigate to Quine in your browser and load all of the nodes that we just created into the Exploration UI. There are multiple ways to load all of the nodes in the UI, for this example, we use `MATCH (n) RETURN n`. The Exploration UI will warn that you are about to render 787 nodes which is correct for all of the icons and grouping nodes generated by the script. Hit the OK button to view the graph.

Note: If you already had Quine open in a browser before running the script, you will need to refresh your browser window to load the new `nodeAppearances` submitted by the query in order for the nodes to render correctly.

In our case, the nodes are jumbled when they are first rendered. Click the play button in the top nav to have Quine organize the graph. Our result produced the graph visualization of all supported icons below:

Icons visualized in Exploration UI, grouped by category.

Conclusion

There you have it, a graph visualization using all of the icons Quine supports!

This script can generate the `nodeAppearances` graph and serve as a starting point if you are looking to automate fetching non-streaming data from websites to enrich streaming data stored in Quine.

RECENT NODES APPEARANCE API screen shot from Quine. Uses the Stoplight framework.

If you want to learn more about Quine or explore using other API libraries with Quine, check out the interactive REST API documentation available via the document icon in the left nav bar. The interactive documentation is a great place to submit API requests. Code samples in popular languages are quickly mocked up in the docs for use when experimenting with small projects like this yourself.

You can download this script and try it for yourself in this GitHub Repo.