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.