{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "989a6e50", "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "try:\n", " import IPython\n", "except:\n", " %pip install IPython\n", " import IPython \n", "from IPython.display import display, IFrame, HTML, Javascript\n", "from IPython.core.display import HTML\n", "HTML(\"\"\"\"\"\")" ] }, { "cell_type": "markdown", "id": "c4b4ad69", "metadata": {}, "source": [ "# Transforming Collection Data to Linked Art\n", "# Indianapolis Museum of Art\n", "\n", "This notebook provided a step through the process of transforming collections data to Linked Art.\n", "\n", "- The notebook uses collections data from the [Indianapolis Museum of Art (IMA)]((https://discovernewfields.org/)). \n", "- You will need to download the IMA collections data file when prompted.\n", "- The coded transformation is based on the transformation encoded in the IMA's XSLT file, available in the [IMA GitHub repository](https://github.com/IMAmuseum/LinkedArt).\n", "- The Linked Art data model documentation included in the notebook has been sourced from the [Linked Art website](https://linked.art).\n", "\n", "\n", "##### Further Reading\n", "\n", "- [Indianapolis Museum of Art (IMA)](https://discovernewfields.org/)\n", "- [IMA GitHub repository](https://github.com/IMAmuseum/LinkedArt) \n", "- [Linked Art website](https://linked.art) \n", "\n", "\n", "## Method\n", "\n", "- Upload collections data file\n", "- Create dropdown of artworks to transform\n", "- Select an artwork from dropdown, to transform to Linked Art\n", "- Map collection data to Linked Art data model\n", "- Transform artwork description to Linked Art\n", "- Visualise Linked Art JSON-LD representation of artwork\n" ] }, { "cell_type": "markdown", "id": "8ecd08d3", "metadata": {}, "source": [ "## Load Collections Data File\n", "\n", "The Indianapolis Museum of Art (IMA) has transformed a sample of its collections data to Linked Art, and originated from the IMA's [EMu Collections Management Systems](https://www.axiell.com/solutions/product/emu/) in XML format. \n", "\n", "- The [XML data files](https://github.com/IMAmuseum/LinkedArt/blob/master/XML) are available via \n", " - [IMA's LinkedArt GitHub repository](https://github.com/IMAmuseum/LinkedArt), \n", " - including the [Objects Sample XML file](https://github.com/IMAmuseum/LinkedArt/blob/master/XML/ObjectsSample.xml) ([raw file](https://raw.githubusercontent.com/IMAmuseum/LinkedArt/master/XML/ObjectsSample.xml)). \n", "\n", "\n", "----\n", "##### Further reading\n", "- IMA's [EMu Collections Management Systems](https://www.axiell.com/solutions/product/emu/)\n", "- [XML data files](https://github.com/IMAmuseum/LinkedArt/blob/master/XML)\n", "- [IMA's LinkedArt GitHub repository](https://github.com/IMAmuseum/LinkedArt)\n", "- [Objects Sample XML file](https://github.com/IMAmuseum/LinkedArt/blob/master/XML/ObjectsSample.xml) ([Objects Sample XML - raw file](https://raw.githubusercontent.com/IMAmuseum/LinkedArt/master/XML/ObjectsSample.xml))\n" ] }, { "cell_type": "markdown", "id": "5f74d2ad", "metadata": {}, "source": [ "The following code that has been commented out, allows you to upload an XML file of your choice. \n", "" ] }, { "cell_type": "code", "execution_count": 2, "id": "700d7c7c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "try:\n", " import ipywidgets\n", "except:\n", " !pip install ipywidgets\n", " import ipywidgets \n", " \n", "from ipywidgets import Layout, FileUpload \n", "\n", "\n", "\"\"\"\n", "# define file upload widget\n", "upload = FileUpload(accept='.xml', multiple=False, description='Select XML file')\n", "upload\n", "\"\"\"\n", "print(\"\")" ] }, { "cell_type": "code", "execution_count": 3, "id": "cb06cfa6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "try:\n", " import xmltodict\n", "except:\n", " !pip install xmltodict\n", " import xmltodict \n", "\n", "\"\"\"\n", "obj = False\n", "# get content from uploaded file \n", "for uploaded_filename in upload.value:\n", " content = upload.value[uploaded_filename]['content']\n", " obj = xmltodict.parse(content) \n", " \n", "if obj == False:\n", " display(HTML(\"
Please select a file to transform
\"))\n", "else:\n", " display(HTML(\"
File uploaded
\"))\n", " \n", "content = upload.value[uploaded_filename]['content']\n", "obj = xmltodict.parse(content) \n", "\"\"\"\n", "print(\"\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "2d7add0d", "metadata": {}, "outputs": [], "source": [ "\n", "with open('data/ima/input/ObjectsSample.xml') as fd:\n", " content = fd.read()\n", " obj = xmltodict.parse(content) \n", "\n", " allObjects = obj[\"table\"][\"tuple\"]\n", "\n", " #object dropdown options\n", " objOptions = []\n", " objOptions.append((\"Please select an object\",''))\n", "\n", " for obj in allObjects:\n", " title = irn = \"\"\n", " # define properties variables \n", " for prop in obj[\"atom\"]: \n", " propName = prop[\"@name\"] \n", " if \"#text\" in prop:\n", " if propName == \"irn\":\n", " irn = prop[\"#text\"] \n", " if propName == \"TitMainTitle\":\n", " title = prop[\"#text\"]\n", " \n", " objOptions.append((irn + ' : ' + title,irn))\n", " \n", " selectObject = ipywidgets.Dropdown(options=objOptions, description='Select Object')" ] }, { "cell_type": "markdown", "id": "cbe11373", "metadata": {}, "source": [ "## Select an Artwork to Transform to Linked Art\n", "The next step will create a dropdown list of artworks from the XML file.\n", "\n", "
Select an artwork from the dropdown list to transform the catalogue data to Linked Art JSON-LD
" ] }, { "cell_type": "code", "execution_count": 6, "id": "907be7ef", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "04f59bb2e6db445c8084bd1b4b07f675", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Dropdown(description='Select Object', index=23, options=(('Please select an object', ''), ('1032 : long-neck v…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Artwork selected : 37383 : Oaks of Vernon" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(selectObject)\n", "\n", "selectedIRN = selectObject.value\n", "if selectedIRN == \"\":\n", " display(HTML(\"
Please select an artwork to transform
\"))\n", "else:\n", " display(HTML(\"
Artwork selected : \" + selectObject.options[selectObject.index][0] + \"\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
0
idirn
accession_numberTitAccessionNo
accession_dateTitAccessionDate
classificationTitObjectType
titleTitMainTitle
alt_title
notesTitTitleNotes
date_createdCreDateCreated
date_created_earliestCreEarliestDate
date_created_latestCreLatestDate
created_periodCreCreationPeriod
created_dynastyCreCreationDynasty
created_inscriptionsCrePrimaryInscriptions
created_notesCreCreationNotes
creator
physical_mediumPhyMediumAndSupport
physical_stylePhyStyle
physical_techniquePhyTechnique
physical_descriptionPhyDescription
physical_dimensionsPhyConvertedDims
created_provenanceCreProvenance
credit_line
collectionPhyCollectionArea
current_status
current_owner
\n", "
" ], "text/plain": [ " 0\n", "id irn\n", "accession_number TitAccessionNo\n", "accession_date TitAccessionDate\n", "classification TitObjectType\n", "title TitMainTitle\n", "alt_title \n", "notes TitTitleNotes\n", "date_created CreDateCreated\n", "date_created_earliest CreEarliestDate\n", "date_created_latest CreLatestDate\n", "created_period CreCreationPeriod\n", "created_dynasty CreCreationDynasty\n", "created_inscriptions CrePrimaryInscriptions\n", "created_notes CreCreationNotes\n", "creator \n", "physical_medium PhyMediumAndSupport\n", "physical_style PhyStyle\n", "physical_technique PhyTechnique\n", "physical_description PhyDescription\n", "physical_dimensions PhyConvertedDims\n", "created_provenance CreProvenance\n", "credit_line \n", "collection PhyCollectionArea\n", "current_status \n", "current_owner " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "try: \n", " import pandas as pd\n", "except:\n", " %pip install pandas \n", " import pandas as pd\n", "\n", "mapp = {\n", " \"id\":\"irn\",\n", " \"accession_number\":\"TitAccessionNo\",\n", " \"accession_date\": \"TitAccessionDate\",\n", " \"classification\" : \"TitObjectType\",\n", " \"title\": \"TitMainTitle\",\n", " \"alt_title\": \"\",\n", " \"notes\": \"TitTitleNotes\",\n", " \"date_created\":\"CreDateCreated\",\n", " \"date_created_earliest\": \"CreEarliestDate\",\n", " \"date_created_latest\": \"CreLatestDate\",\n", " \"created_period\":\"CreCreationPeriod\",\n", " \"created_dynasty\":\"CreCreationDynasty\",\n", " \"created_inscriptions\":\"CrePrimaryInscriptions\",\n", " \"created_notes\": \"CreCreationNotes\",\n", " \"creator\":\"\",\n", " \"physical_medium\": \"PhyMediumAndSupport\",\n", " \"physical_style\": \"PhyStyle\",\n", " \"physical_technique\": \"PhyTechnique\",\n", " \"physical_description\": \"PhyDescription\",\n", " \"physical_dimensions\": \"PhyConvertedDims\",\n", " \"created_provenance\": \"CreProvenance\" ,\n", " \"credit_line\": \"\",\n", " \"collection\" : \"PhyCollectionArea\",\n", " \"current_status\" : \"\",\n", " \"current_owner\" : \"\"\n", "}\n", "\n", "display(pd.DataFrame(mapp, index=[0]).T)\n" ] }, { "cell_type": "markdown", "id": "b211703a", "metadata": {}, "source": [ "### Create dictionary using field mapping\n", "\n", "The next step uses the `mapp` to create a dictionary containing artwork properties from the input data." ] }, { "cell_type": "code", "execution_count": 9, "id": "6c3e5770", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"creator\": [\n", " {\n", " \"id\": \"11566\",\n", " \"name\": \"Steele, Theodore Clement\",\n", " \"role\": \"Artist\"\n", " }\n", " ],\n", " \"id\": \"37383\",\n", " \"accession_number\": \"89.1\",\n", " \"accession_date\": \"1889\",\n", " \"title\": \"Oaks of Vernon\",\n", " \"notes\": \"\",\n", " \"classification\": \"Visual Works: Paintings\",\n", " \"date_created\": \"1887\",\n", " \"date_created_earliest\": \"1887\",\n", " \"date_created_latest\": \"1887\",\n", " \"created_period\": \"\",\n", " \"created_dynasty\": \"\",\n", " \"created_inscriptions\": \"Signed and dated, l.l.: T.C.Steele .87.\",\n", " \"created_notes\": \"\",\n", " \"physical_medium\": \"oil on canvas\",\n", " \"physical_style\": \"\",\n", " \"physical_technique\": \"painting\",\n", " \"physical_description\": \"\",\n", " \"physical_dimensions\": \"30 x 45-1/4 in. (canvas)\\n36-3/4 x 51-3/4 x 3-1/4 in. (framed)\",\n", " \"created_provenance\": \"Purchased from the artist by the John Herron Art Institute, Indianapolis, Indiana, now the Indianapolis Museum of Art in 1889.\",\n", " \"collection\": \"901.2-American Painting and Sculpture 1800-1945\",\n", " \"current_owner\": {\n", " \"name\": \"Indianapolis Museum of Art at Newfields\",\n", " \"location\": \"Indianapolis, Indiana\",\n", " \"type\": \"http://vocab.getty.edu/aat/300312281\",\n", " \"type_label\": \"\"\n", " }\n", "}\n" ] } ], "source": [ "import json\n", "\n", "\n", "#src https://note.nkmk.me/en/python-dict-get-key-from-value/\n", "def get_key_from_value(d, val):\n", " keys = [k for k, v in d.items() if v == val]\n", " if keys:\n", " return keys[0]\n", " return None\n", "\n", "\n", "# map input file data to dictionary keys\n", "def createObjProp(obj,mapp):\n", " objProp = {\"creator\":[]}\n", " for prop in obj[\"atom\"]: \n", " propName = prop[\"@name\"]\n", " propValue = \"\"\n", " if \"#text\" in prop:\n", " propValue = prop[\"#text\"]\n", " if propName in list(mapp.values()):\n", " key = get_key_from_value(mapp, propName)\n", " objProp[key] = propValue\n", " \n", " for table in obj[\"table\"]: \n", " if table[\"@name\"] == \"Creator1\":\n", " id = name = \"\"\n", " if \"tuple\" in table and \"atom\" in table[\"tuple\"]:\n", " for cinfo in table[\"tuple\"][\"atom\"]:\n", " if cinfo[\"@name\"] == \"irn\":\n", " id = cinfo[\"#text\"]\n", " if cinfo[\"@name\"] == \"SummaryData\":\n", " name = cinfo[\"#text\"]\n", " if cinfo[\"@name\"] == \"CreRole\":\n", " role = cinfo[\"#text\"]\n", " objProp[\"creator\"].append({\"id\": id, \"name\": name, \"role\" : role})\n", " \n", " objProp[\"current_owner\"] = {\"name\":\"Indianapolis Museum of Art at Newfields\",\n", " \"location\":\"Indianapolis, Indiana\",\n", " \"type\": \"http://vocab.getty.edu/aat/300312281\" ,\n", " \"type_label\": \"\"}\n", " return objProp\n", "\n", "objProp = createObjProp(obj,mapp)\n", "\n", "# display transposed dataframe of data mapping\n", "print(json.dumps(objProp, indent=2))\n" ] }, { "cell_type": "markdown", "id": "f61df79a", "metadata": {}, "source": [ "## Transform artwork description to Linked Art\n", "\n", "The next section of the notebook transforms the selected artwork description in the collection data to Linked Art. The step through the transformation process is divided into sections, looking at the representation of different entities in the Linked Art data model.\n", "\n", "Initial variables are defined:" ] }, { "cell_type": "code", "execution_count": 10, "id": "062afb11", "metadata": {}, "outputs": [], "source": [ "try:\n", " import cromulent\n", "except:\n", " !pip install cromulent\n", " import cromulent \n", "\n", "from cromulent.model import factory, Actor, Production, BeginningOfExistence, EndOfExistence, TimeSpan, Place\n", "from cromulent.model import InformationObject, Phase, VisualItem \n", "from cromulent.vocab import Painting, Drawing,Miniature,add_art_setter, PrimaryName, Name, CollectionSet, instances, Sculpture \n", "from cromulent.vocab import aat_culture_mapping, AccessionNumber, Height, Width, SupportPart, Gallery, MuseumPlace \n", "from cromulent.vocab import BottomPart, Description, RightsStatement, MuseumOrg, Purchase\n", "from cromulent.vocab import Furniture, Mosaic, Photograph, Coin, Vessel, Graphic, Enamel, Embroidery, PhotographPrint\n", "from cromulent.vocab import PhotographAlbum, PhotographBook, PhotographColor, PhotographBW, Negative, Map, Clothing, Furniture\n", "from cromulent.vocab import Sample, Architecture, Armor, Book, DecArts, Implement, Jewelry, Manuscript, SiteInstallation, Text, Print\n", "from cromulent.vocab import TimeBasedMedia, Page, Folio, Folder, Box, Envelope, Binder, Case, FlatfileCabinet\n", "from cromulent.vocab import HumanMadeObject,Tapestry,LocalNumber\n", "from cromulent.vocab import Type,Set\n", "from cromulent.vocab import TimeSpan, Group, Acquisition, Place\n", "from cromulent.vocab import Production, TimeSpan, Actor\n", "from cromulent.vocab import LinguisticObject\n", "\n", "\n", "# baseURI for JSON-LD document\n", "baseURI = \"https://data.discovernewfields.org/\"\n", "factory.base_url = baseURI\n", "factory.default_lang = \"en\"" ] }, { "cell_type": "markdown", "id": "cb9cadcf", "metadata": {}, "source": [ "### Core Properties and Classification\n", "\n", "This section creates a minimum representation of an artwork in Linked Art including its classification as a particular type of artwork.\n", " \n", "----\n", "\n", "##### Linked Art data model\n", "\n", "There are a few core properties that every resource should have for it to be a useful part of the world of Linked Open Data:\n", "\n", "- `@context`\n", "- `id`\n", "- `type`\n", "- `_label`\n", "\n", "CIDOC-CRM is a framework that must be extended via additional vocabularies and ontologies to be useful. The provided mechanism for doing this is the classified_as property, which refers to a term from a controlled vocabulary. This is in contrast to the `type` property, which is used for CIDOC-CRM defined classes, and a few extensions as needed. \n", "\n", "The `classified_as` property is thus a way to be more specific about the sort of entity, while maintaining the core information as the class using type. Controlled vocabulary entries should not be used with `type`, nor classes used with `classified_as`.\n", "\n", "While any external vocabulary of terms can be used, the Getty's Art and Architecture Thesaurus is used whenever possible for consistency and that it is already widespread in the museum domain. The set of terms that have been identified as useful are listed in the community best-practices for recommendations, and within the documentation of the model when a particular choice is essential for interoperability.\n", "\n", "\n", "-----\n", "\n", "##### IMA collection data mapping\n", "\n", "- The `id` is a URL and has been created from the `irn` value together with a URL prefix: https://data.discovernewfields.org/\n", "- The `_label` is a human readable label, intended for developers and other people reading the data. The value is taken from the `TitMainTitle` property.\n", "- The `classification` property in objProp is used to search for a matching object type. The value is taken from the `TitObjectType` property in the IMA XML data file.\n", "\n", "----\n", "\n", "##### Further reading\n", "\n", "https://linked.art/model/base/#core-properties\n", "\n", "https://linked.art/model/base/#types-and-classifications\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "8e290e25", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/37383\",\n", " \"type\": \"HumanMadeObject\",\n", " \"_label\": \"Oaks of Vernon\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300033618\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Painting\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300435443\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Type of Work\"\n", " }\n", " ]\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "# dictionary of cromulent Linked Art object types\n", "objTypes = {\n", "\"Painting\": Painting(),\n", "\"Sculpture\": Sculpture(),\n", "\"Drawing\": Drawing(),\n", "\"Miniature\": Miniature(),\n", "\"Tapestry\": Tapestry(),\n", "\"Furniture\": Furniture(),\n", "\"Furnishings\": DecArts(),\n", "\"Mosaic\": Mosaic(),\n", "\"Photograph\": Photograph(),\n", "\"Coin\": Coin(),\n", "\"Vessel\": Vessel(),\n", "\"Graphic\": Graphic(),\n", "\"Enamel\": Enamel(),\n", "\"Embroidery\": Embroidery(),\n", "\"PhotographPrint\": PhotographPrint(),\n", "\"PhotographAlbum\": PhotographAlbum(),\n", "\"PhotographBook\": PhotographBook(),\n", "\"PhotographColor\": PhotographColor(),\n", "\"PhotographBW\": PhotographBW(),\n", "\"Negative\": Negative(),\n", "\"Map\": Map(),\n", "\"Clothing\": Clothing(),\n", "\"Sample\": Sample(),\n", "\"Architecture\": Architecture(),\n", "\"Armor\": Armor(),\n", "\"Book\": Book(),\n", "\"DecArts\": DecArts(),\n", "\"Implement\": Implement(),\n", "\"Jewelry\": Jewelry(),\n", "\"Manuscript\": Manuscript(),\n", "\"SiteInstallation\": SiteInstallation(),\n", "\"Text\": Text(),\n", "\"Print\": Print(),\n", "\"TimeBasedMedia\": TimeBasedMedia(),\n", "\"Page\": Page(),\n", "\"Folio\": Folio(),\n", "\"Folder\": Folder(),\n", "\"Box\": Box(),\n", "\"Envelope\": Envelope(),\n", "\"Binder\": Binder(),\n", "\"Case\": Case(),\n", "\"FlatfileCabinet\": FlatfileCabinet()\n", "}\n", "\n", "\n", "object_uri = baseURI + objProp[\"id\"]\n", "\n", "objLA = None\n", "objLA = HumanMadeObject() # linked art object\n", "\n", "for otype in objTypes:\n", " if otype in objProp[\"classification\"]:\n", " objLA = objTypes[otype] \n", " break\n", " \n", "objLA.id = baseURI + objProp[\"id\"]\n", "objLA._label = objProp[\"title\"]\n", "\n", "data = factory.toString(objLA, compact=False)\n", "print(data)\n", "\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "372b031d", "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "\n", "require.config({paths: {\n", " d3: \"http://d3js.org/d3.v3.min\"\n", "}});\n", "\n", "\n", "\n", "function visjsonld(jsonld, selector){\n", " \n", " require([\"d3\"], function(d3) {\n", " \n", " var config = {};\n", " \n", " var h = config.h || 800\n", " , w = config.w || 3000\n", " , maxLabelWidth = config.maxLabelWidth || 200\n", " , transitionDuration = config.transitionDuration || 750\n", " , transitionEase = config.transitionEase || 'cubic-in-out'\n", " , minRadius = config.minRadius || 5\n", " , scalingFactor = config.scalingFactor || 2;\n", " \n", " var i = 0;\n", "\n", " var tree = d3.layout.tree()\n", " .size([h, w]);\n", " \n", " var diagonal = d3.svg.diagonal()\n", " .projection(function(d) { return [d.y, d.x]; });\n", " \n", " d3.select(selector).selectAll(\"svg\").remove();\n", " \n", " var svg = d3.select(selector).append('svg')\n", " .attr('width', w)\n", " .attr('height', h)\n", " .attr('style', 'background-color:#80CBC4 ')\n", " .append('g')\n", " .attr('transform', 'translate(' + maxLabelWidth + ',0)');\n", " \n", " var root = jsonldTree(jsonld);\n", " root.x0 = h / 2;\n", " root.y0 = 0;\n", " root.children.forEach(collapse);\n", " \n", " function changeSVGWidth(newWidth) {\n", " if (w !== newWidth) {\n", " d3.select(selector + ' > svg').attr('width', newWidth);\n", " }\n", " }\n", " \n", " function jsonldTree(source) {\n", " var tree = {};\n", " \n", " if ('@id' in source) {\n", " tree.isIdNode = true;\n", " tree.name = source['@id'];\n", " if (tree.name.length > maxLabelWidth / 9) {\n", " tree.valueExtended = tree.name;\n", " tree.name = '...' + tree.valueExtended.slice(-Math.floor(maxLabelWidth / 9));\n", " }\n", " } else {\n", " tree.isIdNode = true;\n", " tree.isBlankNode = true;\n", " // random id, can replace with actual uuid generator if needed\n", " tree.name = '_' + Math.random().toString(10).slice(-7);\n", " }\n", " \n", " var children = [];\n", " Object.keys(source).forEach(function(key) {\n", " if (key === '@id' || key === '@context' || source[key] === null) return;\n", " \n", " var valueExtended, value;\n", " if (typeof source[key] === 'object' && !Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: [jsonldTree(source[key])]\n", " });\n", " } else if (Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: source[key].map(function(item) {\n", " if (typeof item === 'object') {\n", " return jsonldTree(item);\n", " } else {\n", " return { name: item };\n", " }\n", " })\n", " });\n", " } else {\n", " valueExtended = source[key];\n", " value = valueExtended;\n", " if (value.length > maxLabelWidth / 9) {\n", " value = value.slice(0, Math.floor(maxLabelWidth / 2)) + '...';\n", " children.push({\n", " name: key,\n", " value: value,\n", " valueExtended: valueExtended\n", " });\n", " } else {\n", " children.push({\n", " name: key,\n", " value: value\n", " });\n", " }\n", " }\n", " });\n", " \n", " if (children.length) {\n", " tree.children = children;\n", " }\n", " \n", " return tree;\n", " }\n", " \n", " function update(source) {\n", " var nodes = tree.nodes(root).reverse();\n", " var links = tree.links(nodes);\n", " \n", " nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; });\n", " \n", " var node = svg.selectAll('g.node')\n", " .data(nodes, function(d) { return d.id || (d.id = ++i); });\n", " \n", " var nodeEnter = node.enter()\n", " .append('g')\n", " .attr('class', 'node')\n", " .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; })\n", " .on('click', click);\n", " \n", " nodeEnter.append('circle')\n", " .attr('r', 0)\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#F7CA18' : '#4ECDC4';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? '#F5D76E' : 'white';\n", " } else {\n", " return d._children ? '#86E2D5' : 'white';\n", " }\n", " })\n", " \n", " \n", " nodeEnter.append('text')\n", " .attr('x', function(d) {\n", " var spacing = computeRadius(d) + 5;\n", " return d.children || d._children ? -spacing : spacing;\n", " })\n", " .attr('dy', '4')\n", " .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })\n", " .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })\n", " .style('fill-opacity', 0)\n", ";\n", " \n", " var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; }));\n", " if (maxSpan + maxLabelWidth + 20 > w) {\n", " changeSVGWidth(maxSpan + maxLabelWidth);\n", " d3.select(selector).node().scrollLeft = source.y0;\n", " }\n", " \n", " var nodeUpdate = node.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });\n", " \n", " nodeUpdate.select('circle')\n", " .attr('r', function(d) { return computeRadius(d); })\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#F7CA18' : '#4ECDC4';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? '#F5D76E' : 'white';\n", " } else {\n", " return d._children ? '#86E2D5' : 'navy';\n", " }\n", " });\n", " \n", " nodeUpdate.select('text').style('fill-opacity', 1);\n", " nodeUpdate.select('text').style('font-family', \"'Open Sans', 'Helvetica Neue', Helvetica, sans-serif\");\n", " nodeUpdate.select('text').style('fill', '#333333');\n", " nodeUpdate.select('text').style('font-size', '12px');\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " var nodeExit = node.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })\n", " .remove();\n", " \n", " nodeExit.select('circle').attr('r', 0);\n", " nodeExit.select('text').style('fill-opacity', 0);\n", " \n", " var link = svg.selectAll('path.link')\n", " .data(links, function(d) { return d.target.id; });\n", " \n", " link.enter().insert('path', 'g')\n", " .attr('class', 'link')\n", " .attr('style', 'fill: none;stroke: #DADFE1;stroke-width: 1px;')\n", " .attr('d', function(d) {\n", " var o = { x: source.x0, y: source.y0 };\n", " return diagonal({ source: o, target: o });\n", " });\n", " \n", " link.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', diagonal);\n", " \n", " link.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', function(d) {\n", " var o = { x: source.x, y: source.y };\n", " return diagonal({ source: o, target: o });\n", " })\n", " .remove();\n", " \n", " nodes.forEach(function(d) {\n", " d.x0 = d.x;\n", " d.y0 = d.y;\n", " });\n", " }\n", " \n", " function computeRadius(d) {\n", " if (d.children || d._children) {\n", " return minRadius + (numEndNodes(d) / scalingFactor);\n", " } else {\n", " return minRadius;\n", " }\n", " }\n", " \n", " function numEndNodes(n) {\n", " var num = 0;\n", " if (n.children) {\n", " n.children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else if (n._children) {\n", " n._children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else {\n", " num++;\n", " }\n", " return num;\n", " }\n", " \n", " function click(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d.children = null;\n", " } else {\n", " d.children = d._children;\n", " d._children = null;\n", " }\n", " \n", " update(d);\n", " \n", " // fast-forward blank nodes\n", " if (d.children) {\n", " d.children.forEach(function(child) {\n", " if (child.isBlankNode && child._children) {\n", " click(child);\n", " }\n", " });\n", " }\n", " }\n", " \n", " function collapse(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d._children.forEach(collapse);\n", " d.children = null;\n", " }\n", " }\n", " \n", " update(root);\n", " \n", " \n", " })\n", " }\n", "var data = {\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/37383\",\n", " \"type\": \"HumanMadeObject\",\n", " \"_label\": \"Oaks of Vernon\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300033618\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Painting\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300435443\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Type of Work\"\n", " }\n", " ]\n", " }\n", " ]\n", "}; var selector = \"#vis\"; visjsonld(data, selector); " ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with open('src/js/visld2.js', 'r') as _jscript:\n", " code = _jscript.read() + 'var data = ' + data + '; var selector = \"#vis\"; visjsonld(data, selector); ' \n", "Javascript(code)" ] }, { "cell_type": "markdown", "id": "c9165bd8", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "id": "f04e6fc2", "metadata": {}, "source": [ "### Identifiers \n", "\n", "This section creates representations of different types of identifiers for the artwork, e.g. accession number.\n", "\n", "----\n", "\n", "##### Linked Art data model\n", "\n", "Many resources of interest are given external identifiers, such as accession numbers for objects, ORCIDs for people or groups, lot numbers for auctions, and so forth. Identifiers are represented in a very similar way to names, but instead use the Identifier class. Identifiers will normally have a classification determining which sort of identifier it is, to distinguish between internal repository system assigned numbers from museum assigned accession numbers, for example.\n", "\n", "As Identifiers and Names use the same `identified_by` property, the JSON will frequently have mixed classes in the array. Unlike `Names`, `Identifiers` are not part of human language and thus cannot have translations or a language associated with them.\n", "\n", "-----\n", "##### IMA collection data mapping\n", "\n", "- `accession_number` is the IMA data accession number\n", "- `owner assigned number` is the IMA data id\n", "\n", "----\n", "##### Further reading\n", "\n", "https://linked.art/model/base/#identifiers\n" ] }, { "cell_type": "markdown", "id": "cab7fc4b", "metadata": {}, "source": [ "### Accession Number\n", "\n", "This section represents the artwork's accession number.\n", "\n", "An accession number is a sequential number assigned to each record or item as it is added to a to a library collection or database and which indicates the chronological order of its acquisition (src: https://libanswers.liverpool.ac.uk/faq/181287).\n", "\n", "\n", "\n", "---- \n", "##### Further reading\n", "\n", "https://linked.art/model/base/#identifiers" ] }, { "cell_type": "code", "execution_count": 13, "id": "3db9a04a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/Identifier/89.1\",\n", " \"type\": \"Identifier\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300312355\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Accession Number\"\n", " }\n", " ],\n", " \"content\": \"89.1\"\n", "}\n" ] } ], "source": [ "import json \n", "def objAccession(objProp,object_uri):\n", " accession = None\n", " accession_number = objProp[\"accession_number\"]\n", " if accession_number != \"\":\n", " accession = AccessionNumber(accession_number,value=accession_number)\n", " return accession\n", "\n", "\n", "data = factory.toString(objAccession(objProp,object_uri), compact=False)\n", "\n", "print(data)" ] }, { "cell_type": "code", "execution_count": 14, "id": "1cf334ea", "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "\n", "require.config({paths: {\n", " d3: \"http://d3js.org/d3.v3.min\"\n", "}});\n", "\n", "\n", "\n", "function visjsonld(jsonld, selector){\n", " \n", " require([\"d3\"], function(d3) {\n", " \n", " var config = {};\n", " \n", " var h = config.h || 800\n", " , w = config.w || 3000\n", " , maxLabelWidth = config.maxLabelWidth || 200\n", " , transitionDuration = config.transitionDuration || 750\n", " , transitionEase = config.transitionEase || 'cubic-in-out'\n", " , minRadius = config.minRadius || 5\n", " , scalingFactor = config.scalingFactor || 2;\n", " \n", " var i = 0;\n", "\n", " var tree = d3.layout.tree()\n", " .size([h, w]);\n", " \n", " var diagonal = d3.svg.diagonal()\n", " .projection(function(d) { return [d.y, d.x]; });\n", " \n", " d3.select(selector).selectAll(\"svg\").remove();\n", " \n", " var svg = d3.select(selector).append('svg')\n", " .attr('width', w)\n", " .attr('height', h)\n", " .attr('style', 'background-color:#80CBC4 ')\n", " .append('g')\n", " .attr('transform', 'translate(' + maxLabelWidth + ',0)');\n", " \n", " var root = jsonldTree(jsonld);\n", " root.x0 = h / 2;\n", " root.y0 = 0;\n", " root.children.forEach(collapse);\n", " \n", " function changeSVGWidth(newWidth) {\n", " if (w !== newWidth) {\n", " d3.select(selector + ' > svg').attr('width', newWidth);\n", " }\n", " }\n", " \n", " function jsonldTree(source) {\n", " var tree = {};\n", " \n", " if ('@id' in source) {\n", " tree.isIdNode = true;\n", " tree.name = source['@id'];\n", " if (tree.name.length > maxLabelWidth / 9) {\n", " tree.valueExtended = tree.name;\n", " tree.name = '...' + tree.valueExtended.slice(-Math.floor(maxLabelWidth / 9));\n", " }\n", " } else {\n", " tree.isIdNode = true;\n", " tree.isBlankNode = true;\n", " // random id, can replace with actual uuid generator if needed\n", " tree.name = '_' + Math.random().toString(10).slice(-7);\n", " }\n", " \n", " var children = [];\n", " Object.keys(source).forEach(function(key) {\n", " if (key === '@id' || key === '@context' || source[key] === null) return;\n", " \n", " var valueExtended, value;\n", " if (typeof source[key] === 'object' && !Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: [jsonldTree(source[key])]\n", " });\n", " } else if (Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: source[key].map(function(item) {\n", " if (typeof item === 'object') {\n", " return jsonldTree(item);\n", " } else {\n", " return { name: item };\n", " }\n", " })\n", " });\n", " } else {\n", " valueExtended = source[key];\n", " value = valueExtended;\n", " if (value.length > maxLabelWidth / 9) {\n", " value = value.slice(0, Math.floor(maxLabelWidth / 2)) + '...';\n", " children.push({\n", " name: key,\n", " value: value,\n", " valueExtended: valueExtended\n", " });\n", " } else {\n", " children.push({\n", " name: key,\n", " value: value\n", " });\n", " }\n", " }\n", " });\n", " \n", " if (children.length) {\n", " tree.children = children;\n", " }\n", " \n", " return tree;\n", " }\n", " \n", " function update(source) {\n", " var nodes = tree.nodes(root).reverse();\n", " var links = tree.links(nodes);\n", " \n", " nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; });\n", " \n", " var node = svg.selectAll('g.node')\n", " .data(nodes, function(d) { return d.id || (d.id = ++i); });\n", " \n", " var nodeEnter = node.enter()\n", " .append('g')\n", " .attr('class', 'node')\n", " .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; })\n", " .on('click', click);\n", " \n", " nodeEnter.append('circle')\n", " .attr('r', 0)\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#F7CA18' : '#4ECDC4';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? '#F5D76E' : 'white';\n", " } else {\n", " return d._children ? '#86E2D5' : 'white';\n", " }\n", " })\n", " \n", " \n", " nodeEnter.append('text')\n", " .attr('x', function(d) {\n", " var spacing = computeRadius(d) + 5;\n", " return d.children || d._children ? -spacing : spacing;\n", " })\n", " .attr('dy', '4')\n", " .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })\n", " .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })\n", " .style('fill-opacity', 0)\n", ";\n", " \n", " var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; }));\n", " if (maxSpan + maxLabelWidth + 20 > w) {\n", " changeSVGWidth(maxSpan + maxLabelWidth);\n", " d3.select(selector).node().scrollLeft = source.y0;\n", " }\n", " \n", " var nodeUpdate = node.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });\n", " \n", " nodeUpdate.select('circle')\n", " .attr('r', function(d) { return computeRadius(d); })\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#F7CA18' : '#4ECDC4';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? '#F5D76E' : 'white';\n", " } else {\n", " return d._children ? '#86E2D5' : 'navy';\n", " }\n", " });\n", " \n", " nodeUpdate.select('text').style('fill-opacity', 1);\n", " nodeUpdate.select('text').style('font-family', \"'Open Sans', 'Helvetica Neue', Helvetica, sans-serif\");\n", " nodeUpdate.select('text').style('fill', '#333333');\n", " nodeUpdate.select('text').style('font-size', '12px');\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " var nodeExit = node.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })\n", " .remove();\n", " \n", " nodeExit.select('circle').attr('r', 0);\n", " nodeExit.select('text').style('fill-opacity', 0);\n", " \n", " var link = svg.selectAll('path.link')\n", " .data(links, function(d) { return d.target.id; });\n", " \n", " link.enter().insert('path', 'g')\n", " .attr('class', 'link')\n", " .attr('style', 'fill: none;stroke: #DADFE1;stroke-width: 1px;')\n", " .attr('d', function(d) {\n", " var o = { x: source.x0, y: source.y0 };\n", " return diagonal({ source: o, target: o });\n", " });\n", " \n", " link.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', diagonal);\n", " \n", " link.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', function(d) {\n", " var o = { x: source.x, y: source.y };\n", " return diagonal({ source: o, target: o });\n", " })\n", " .remove();\n", " \n", " nodes.forEach(function(d) {\n", " d.x0 = d.x;\n", " d.y0 = d.y;\n", " });\n", " }\n", " \n", " function computeRadius(d) {\n", " if (d.children || d._children) {\n", " return minRadius + (numEndNodes(d) / scalingFactor);\n", " } else {\n", " return minRadius;\n", " }\n", " }\n", " \n", " function numEndNodes(n) {\n", " var num = 0;\n", " if (n.children) {\n", " n.children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else if (n._children) {\n", " n._children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else {\n", " num++;\n", " }\n", " return num;\n", " }\n", " \n", " function click(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d.children = null;\n", " } else {\n", " d.children = d._children;\n", " d._children = null;\n", " }\n", " \n", " update(d);\n", " \n", " // fast-forward blank nodes\n", " if (d.children) {\n", " d.children.forEach(function(child) {\n", " if (child.isBlankNode && child._children) {\n", " click(child);\n", " }\n", " });\n", " }\n", " }\n", " \n", " function collapse(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d._children.forEach(collapse);\n", " d.children = null;\n", " }\n", " }\n", " \n", " update(root);\n", " \n", " \n", " })\n", " }\n", "var data = {\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/Identifier/89.1\",\n", " \"type\": \"Identifier\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300312355\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Accession Number\"\n", " }\n", " ],\n", " \"content\": \"89.1\"\n", "}; var selector = \"#vis-an\"; visjsonld(data, selector); " ], "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with open('src/js/visld2.js', 'r') as _jscript:\n", " code = _jscript.read() + 'var data = ' + data + '; var selector = \"#vis-an\"; visjsonld(data, selector); ' \n", "Javascript(code)" ] }, { "cell_type": "markdown", "id": "13d94321", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "id": "8144b1a9", "metadata": {}, "source": [ "### Local Number\n", "\n", "This section represents and artwork's local number.\n", "\n", "A local number is an organisation-assigned number for an artwork that has a local scope, i.e. that is not global in scope and is not intended to have meaning beyond the organisation.\n", "\n", "---- \n", "##### Further reading\n", "\n", "https://linked.art/model/base/#identifiers" ] }, { "cell_type": "code", "execution_count": 15, "id": "e2f793ee", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/Identifier/37383\",\n", " \"type\": \"Identifier\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300404621\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Owner-Assigned Number\"\n", " }\n", " ],\n", " \"content\": \"37383\"\n", "}\n" ] } ], "source": [ "def objLocalnumber(objProp,object_uri):\n", " localnumber = None\n", " id = str(objProp[\"id\"])\n", " if id != \"\":\n", " localnumber = LocalNumber(id,value=id)\n", " return localnumber\n", "\n", "data = (factory.toString(objLocalnumber(objProp,object_uri), compact=False))\n", "print(data)" ] }, { "cell_type": "code", "execution_count": 16, "id": "a511708f", "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "\n", "require.config({paths: {\n", " d3: \"http://d3js.org/d3.v3.min\"\n", "}});\n", "\n", "\n", "\n", "function visjsonld(jsonld, selector){\n", " \n", " require([\"d3\"], function(d3) {\n", " \n", " var config = {};\n", " \n", " var h = config.h || 800\n", " , w = config.w || 3000\n", " , maxLabelWidth = config.maxLabelWidth || 200\n", " , transitionDuration = config.transitionDuration || 750\n", " , transitionEase = config.transitionEase || 'cubic-in-out'\n", " , minRadius = config.minRadius || 5\n", " , scalingFactor = config.scalingFactor || 2;\n", " \n", " var i = 0;\n", "\n", " var tree = d3.layout.tree()\n", " .size([h, w]);\n", " \n", " var diagonal = d3.svg.diagonal()\n", " .projection(function(d) { return [d.y, d.x]; });\n", " \n", " d3.select(selector).selectAll(\"svg\").remove();\n", " \n", " var svg = d3.select(selector).append('svg')\n", " .attr('width', w)\n", " .attr('height', h)\n", " .attr('style', 'background-color:#80CBC4 ')\n", " .append('g')\n", " .attr('transform', 'translate(' + maxLabelWidth + ',0)');\n", " \n", " var root = jsonldTree(jsonld);\n", " root.x0 = h / 2;\n", " root.y0 = 0;\n", " root.children.forEach(collapse);\n", " \n", " function changeSVGWidth(newWidth) {\n", " if (w !== newWidth) {\n", " d3.select(selector + ' > svg').attr('width', newWidth);\n", " }\n", " }\n", " \n", " function jsonldTree(source) {\n", " var tree = {};\n", " \n", " if ('@id' in source) {\n", " tree.isIdNode = true;\n", " tree.name = source['@id'];\n", " if (tree.name.length > maxLabelWidth / 9) {\n", " tree.valueExtended = tree.name;\n", " tree.name = '...' + tree.valueExtended.slice(-Math.floor(maxLabelWidth / 9));\n", " }\n", " } else {\n", " tree.isIdNode = true;\n", " tree.isBlankNode = true;\n", " // random id, can replace with actual uuid generator if needed\n", " tree.name = '_' + Math.random().toString(10).slice(-7);\n", " }\n", " \n", " var children = [];\n", " Object.keys(source).forEach(function(key) {\n", " if (key === '@id' || key === '@context' || source[key] === null) return;\n", " \n", " var valueExtended, value;\n", " if (typeof source[key] === 'object' && !Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: [jsonldTree(source[key])]\n", " });\n", " } else if (Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: source[key].map(function(item) {\n", " if (typeof item === 'object') {\n", " return jsonldTree(item);\n", " } else {\n", " return { name: item };\n", " }\n", " })\n", " });\n", " } else {\n", " valueExtended = source[key];\n", " value = valueExtended;\n", " if (value.length > maxLabelWidth / 9) {\n", " value = value.slice(0, Math.floor(maxLabelWidth / 2)) + '...';\n", " children.push({\n", " name: key,\n", " value: value,\n", " valueExtended: valueExtended\n", " });\n", " } else {\n", " children.push({\n", " name: key,\n", " value: value\n", " });\n", " }\n", " }\n", " });\n", " \n", " if (children.length) {\n", " tree.children = children;\n", " }\n", " \n", " return tree;\n", " }\n", " \n", " function update(source) {\n", " var nodes = tree.nodes(root).reverse();\n", " var links = tree.links(nodes);\n", " \n", " nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; });\n", " \n", " var node = svg.selectAll('g.node')\n", " .data(nodes, function(d) { return d.id || (d.id = ++i); });\n", " \n", " var nodeEnter = node.enter()\n", " .append('g')\n", " .attr('class', 'node')\n", " .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; })\n", " .on('click', click);\n", " \n", " nodeEnter.append('circle')\n", " .attr('r', 0)\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#F7CA18' : '#4ECDC4';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? '#F5D76E' : 'white';\n", " } else {\n", " return d._children ? '#86E2D5' : 'white';\n", " }\n", " })\n", " \n", " \n", " nodeEnter.append('text')\n", " .attr('x', function(d) {\n", " var spacing = computeRadius(d) + 5;\n", " return d.children || d._children ? -spacing : spacing;\n", " })\n", " .attr('dy', '4')\n", " .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })\n", " .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })\n", " .style('fill-opacity', 0)\n", ";\n", " \n", " var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; }));\n", " if (maxSpan + maxLabelWidth + 20 > w) {\n", " changeSVGWidth(maxSpan + maxLabelWidth);\n", " d3.select(selector).node().scrollLeft = source.y0;\n", " }\n", " \n", " var nodeUpdate = node.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });\n", " \n", " nodeUpdate.select('circle')\n", " .attr('r', function(d) { return computeRadius(d); })\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#F7CA18' : '#4ECDC4';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? '#F5D76E' : 'white';\n", " } else {\n", " return d._children ? '#86E2D5' : 'navy';\n", " }\n", " });\n", " \n", " nodeUpdate.select('text').style('fill-opacity', 1);\n", " nodeUpdate.select('text').style('font-family', \"'Open Sans', 'Helvetica Neue', Helvetica, sans-serif\");\n", " nodeUpdate.select('text').style('fill', '#333333');\n", " nodeUpdate.select('text').style('font-size', '12px');\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " var nodeExit = node.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })\n", " .remove();\n", " \n", " nodeExit.select('circle').attr('r', 0);\n", " nodeExit.select('text').style('fill-opacity', 0);\n", " \n", " var link = svg.selectAll('path.link')\n", " .data(links, function(d) { return d.target.id; });\n", " \n", " link.enter().insert('path', 'g')\n", " .attr('class', 'link')\n", " .attr('style', 'fill: none;stroke: #DADFE1;stroke-width: 1px;')\n", " .attr('d', function(d) {\n", " var o = { x: source.x0, y: source.y0 };\n", " return diagonal({ source: o, target: o });\n", " });\n", " \n", " link.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', diagonal);\n", " \n", " link.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', function(d) {\n", " var o = { x: source.x, y: source.y };\n", " return diagonal({ source: o, target: o });\n", " })\n", " .remove();\n", " \n", " nodes.forEach(function(d) {\n", " d.x0 = d.x;\n", " d.y0 = d.y;\n", " });\n", " }\n", " \n", " function computeRadius(d) {\n", " if (d.children || d._children) {\n", " return minRadius + (numEndNodes(d) / scalingFactor);\n", " } else {\n", " return minRadius;\n", " }\n", " }\n", " \n", " function numEndNodes(n) {\n", " var num = 0;\n", " if (n.children) {\n", " n.children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else if (n._children) {\n", " n._children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else {\n", " num++;\n", " }\n", " return num;\n", " }\n", " \n", " function click(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d.children = null;\n", " } else {\n", " d.children = d._children;\n", " d._children = null;\n", " }\n", " \n", " update(d);\n", " \n", " // fast-forward blank nodes\n", " if (d.children) {\n", " d.children.forEach(function(child) {\n", " if (child.isBlankNode && child._children) {\n", " click(child);\n", " }\n", " });\n", " }\n", " }\n", " \n", " function collapse(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d._children.forEach(collapse);\n", " d.children = null;\n", " }\n", " }\n", " \n", " update(root);\n", " \n", " \n", " })\n", " }\n", "var data = {\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/Identifier/37383\",\n", " \"type\": \"Identifier\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300404621\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Owner-Assigned Number\"\n", " }\n", " ],\n", " \"content\": \"37383\"\n", "}; var selector = \"#vis-ln\"; visjsonld(data, selector); " ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with open('src/js/visld2.js', 'r') as _jscript:\n", " code = _jscript.read() + 'var data = ' + data + '; var selector = \"#vis-ln\"; visjsonld(data, selector); ' \n", "Javascript(code)" ] }, { "cell_type": "markdown", "id": "45ae78e4", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": null, "id": "19a77a25", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "7554143e", "metadata": {}, "source": [ "### Names \n", "\n", "This section is concerned with names for the artwork.\n", "\n", "---- \n", "##### Linked Art data model\n", "\n", "As the `_label` property is intended as internal documentation for the data, it is strongly recommended that every resource that should be rendered to an end user also have at least one specific name. The name could be for an object, a person, a group, an event or anything else. This pattern uses the `identified_by` property, with a `Name` resource. The value of the name is given in the content property of the `Name`.\n", "\n", "It is somewhat unintuitive to think of a name as identifying the resource it is associated with, as names are typically not unique. However, as the name itself is uniquely identified rather than just an anonymous string, they are no longer a shared label and instead the particular instance of a name is uniquely associated with the resource. With this formulation, the name instance does uniquely identify the resource.\n", "\n", "If there is more than one name given, then there should be one that is `classified_as` the primary name for use. This is done by adding the `Primary Name` (aat:300404670) term to it. There should be exactly one primary title given per language.\n", "\n", "Names are also part of human communication, and can have the Linguistic features of the model associated with them, such as having a particular language, or having translations.\n", "\n", "---\n", "##### Further reading\n", "\n", "https://linked.art/model/base/#names\n" ] }, { "cell_type": "markdown", "id": "96f0c7be", "metadata": {}, "source": [ "#### Alternate Name" ] }, { "cell_type": "code", "execution_count": 17, "id": "12a761b1", "metadata": {}, "outputs": [], "source": [ "def objAlternatename(objProp,object_uri):\n", " alternateName = None\n", " if \"alt_title\" in objProp:\n", " alt_title = objProp[\"alt_title\"]\n", " alternatename = AlternateName(object_uri + \"/alternate-name\",value=alt_title)\n", " return alternateName\n", "\n", "altname = objAlternatename(objProp,object_uri)\n", "if altname is not None:\n", " print(factory.toString(altname, compact=False))" ] }, { "cell_type": "markdown", "id": "5791297e", "metadata": {}, "source": [ "### Home Page\n", "\n", "This section is concerned with a home page that describes the artwork.\n", "\n", "----\n", "##### Linked Art data model\n", "\n", "A very common scenario is that there is a web page about the object, perhaps managed by a collections management system. For humans, this page is much more useful than the data intended for machines. It can be referenced with the `subject_of` property, and points to a `DigitalObject` which is `classified_as` a web page, or `aat:300264578`. As with digital images, the home page can have a format of \"text/html\" and other properties.\n", "\n", "----\n", "##### IMA data mapping\n", "- A URL is constructred using a URL prefix `http://collection.imamuseum.org/artwork/` and the artwork's `id` property\n", "\n", "----\n", "\n", "##### Further information\n", "\n", "https://linked.art/model/digital/#home-page" ] }, { "cell_type": "code", "execution_count": 18, "id": "1490e45d", "metadata": {}, "outputs": [], "source": [ "def objHomepage(objProp,object_uri):\n", "\n", " homepage = None\n", " id = str(objProp[\"id\"])\n", "\n", " homepageId = \"http://collection.imamuseum.org/artwork/\" + id\n", "\n", " homepage = LinguisticObject(homepageId, label=\"Homepage for the Object\")\n", " homepage.classified_as = Type(\"http://vocab/getty.edu/aat/300264578\", label=\"Web pages (documents)\")\n", " homepage.classified_as = Type(\"http://vocab.getty.edu/aat/300266277\", label=\"home pages\")\n", " homepage.format = \"text/html\"\n", " \n", " return homepage\n" ] }, { "cell_type": "code", "execution_count": 19, "id": "75cbb51b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"http://collection.imamuseum.org/artwork/37383\",\n", " \"type\": \"LinguisticObject\",\n", " \"_label\": \"Homepage for the Object\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab/getty.edu/aat/300264578\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Web pages (documents)\"\n", " },\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300266277\",\n", " \"type\": \"Type\",\n", " \"_label\": \"home pages\"\n", " }\n", " ],\n", " \"format\": \"text/html\"\n", "}\n" ] } ], "source": [ "homepage = objHomepage(objProp,object_uri) \n", "\n", "if homepage is not None:\n", " print(factory.toString(homepage, compact=False))\n", " " ] }, { "cell_type": "markdown", "id": "7a67b937", "metadata": {}, "source": [ "### Statements about a Resource - Linguistic Objects \n", "\n", "This section is concerns with the representation of assertions about the artwork.\n", "\n", "-----\n", "##### Linked Art data model\n", "\n", "In many cases, current data does not support the level of specificity that the full ontology allows, or the information is simply best expressed in human-readable form. For example, instead of a completely modeled set of parts with materials, many museum collection management systems allow only a single human-readable string for the \"medium\" or \"materials statement\". The same is true in many other situations, including rights or allowable usage statements, dimensions, edition statements and so forth. Any time that there is a description of the resource, with or without qualification as to the type of description, then this pattern can be used to record the descriptive text.\n", "\n", "The pattern makes use of the `LinguisticObject` class that is used to identify a particular piece of textual content. These Linguistic Objects are then refered to by any other resource. They maintain the statement's text in the content property, and the language of the statement (if known) in the language property.\n", "\n", "Use cases for this pattern include:\n", "\n", "- General description of the resource\n", "- Materials statement for an object\n", "- Attribution statement for an image\n", "- Biography for a person\n", "- Dimensions statement for a part of an object \n", "\n", "-----\n", "##### IMA data mapping\n", "- The IMA data field `created_provenance` is used to construct a provenance statement about the artwork\n", "- The IMA data field `credit_line` is used to construct a credit line statement about the artwork\n", "\n", "----\n", "\n", "##### Further reading\n", "\n", "https://linked.art/model/base/#statements-about-a-resource\n" ] }, { "cell_type": "markdown", "id": "ca434e8a", "metadata": {}, "source": [ "#### Provenance\n", "\n", "This code creates a provenance statement about the artwork." ] }, { "cell_type": "code", "execution_count": 20, "id": "139935a1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/37383/provenance-statement\",\n", " \"type\": \"LinguisticObject\",\n", " \"_label\": \"Provenance Statement about the Object\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300055863\",\n", " \"type\": \"Type\",\n", " \"_label\": \"provenance (history of ownership)\"\n", " },\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300418049\",\n", " \"type\": \"Type\",\n", " \"_label\": \"brief texts\"\n", " }\n", " ],\n", " \"content\": \"Purchased from the artist by the John Herron Art Institute, Indianapolis, Indiana, now the Indianapolis Museum of Art in 1889.\"\n", "}\n" ] } ], "source": [ "def objProvenance(objProp,object_uri):\n", "\n", " prov = None\n", " if \"created_provenance\" in objProp:\n", " provenance = objProp[\"created_provenance\"]\n", " if provenance !=\"\":\n", " prov = LinguisticObject(object_uri + \"/provenance-statement\", \n", " value=provenance,\n", " label=\"Provenance Statement about the Object\"\n", " )\n", " prov.classified_as = Type(\"http://vocab.getty.edu/aat/300055863\", label=\"provenance (history of ownership)\")\n", " prov.classified_as = Type(\"http://vocab.getty.edu/aat/300418049\", label=\"brief texts\")\n", " return prov\n", "\n", "prov = objProvenance(objProp,object_uri)\n", "\n", "if prov is not None:\n", " print(factory.toString(prov, compact=False))\n", " \n" ] }, { "cell_type": "markdown", "id": "41e6e10b", "metadata": {}, "source": [ "#### Credit Line\n", "\n", "This code created a credit link statement for the artwork." ] }, { "cell_type": "code", "execution_count": 21, "id": "2a751e47", "metadata": {}, "outputs": [], "source": [ "def objCredit(objProp,object_uri):\n", " credit = None\n", " propCredit = \"credit_line\"\n", " \n", " if propCredit in objProp:\n", " credit_line = objProp[propCredit]\n", " if credit_line != \"\":\n", " credit = LinguisticObject(object_uri + \"/credit-line\", \n", " value=credit_line,\n", " label=\"Credit Line for the Object\"\n", " )\n", " credit.classified_as = Type(\"http://vocab.getty.edu/aat/300026687\", label=\"acknowledgements\")\n", " credit.classified_as = Type(\"http://vocab.getty.edu/aat/300418049\", label=\"brief texts\")\n", " return credit\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "83d17d48", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "No credit description\n" ] } ], "source": [ "credit = objCredit(objProp,object_uri)\n", "\n", "if credit is not None:\n", " print(factory.toString(credit, compact=False))\n", "else: \n", " print(\"No credit description\")" ] }, { "cell_type": "markdown", "id": "60456c6e", "metadata": {}, "source": [ "### Production \n", "\n", "This section is concerned with a representation in Linked Art of the production of the artwork.\n", "\n", "----\n", "\n", "##### Linked Art data model \n", "\n", "The first activity in an object's lifecycle is its creation, or `Production`. The relationship to the object that was produced by the activity (`produced`) is added to the general activity model, along with the time, location and actors. This follows the base pattern for activities.\n", "\n", "\n", "----\n", "##### IMA data mapping\n", "\n", "- The IMA data fields are used in the production event representation\n", " - `date_created`\n", " - `date_created_earliest`\n", " - `date_created_latest`\n", "- If a value exists for the `creator` field in the `mapp` dictionary, this is used for the representation of the creator role in the production event.\n", "\n", "----\n", "##### Further reading\n", "\n", "https://linked.art/model/object/production/\n" ] }, { "cell_type": "code", "execution_count": 23, "id": "a109e578", "metadata": {}, "outputs": [], "source": [ "def objProduction(objProp,object_uri):\n", "\n", " prod = None\n", "\n", " date_created = \"date_created\"\n", " created_earliest = \"date_created_earliest\"\n", " created_latest = \"date_created_latest\"\n", "\n", " if date_created in objProp:\n", " prod = Production(object_uri + \"/production\", label=\"Production of the Object\") \n", " \n", " labelTimespan = \"date unknown\"\n", " if objProp[date_created] != \"\":\n", " labelTimespan = objProp[date_created]\n", " \n", " timespan = TimeSpan(object_uri + \"/production/timespan\", label=labelTimespan)\n", " \n", " if created_earliest in objProp:\n", " timespan.begin_of_the_begin = objProp[created_earliest]\n", " if created_latest in objProp:\n", " timespan.end_of_the_end = objProp[created_latest]\n", " \n", " prod.timespan = timespan \n", " propCreator = \"creator\" \n", " if propCreator in objProp: \n", " creators = objProp[propCreator]\n", " id = label = \"\"\n", " for creator in creators:\n", " for prop in creator:\n", " if \"id\" == prop:\n", " id = creator[\"id\"]\n", " if \"name\" == prop:\n", " label = creator[\"name\"]\n", " if label != \"\": \n", " actor = Actor(id,label)\n", " prod.carried_out_by = actor\n", " \n", " return prod" ] }, { "cell_type": "code", "execution_count": 24, "id": "c94ff249", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/37383/production\",\n", " \"type\": \"Production\",\n", " \"_label\": \"Production of the Object\",\n", " \"timespan\": {\n", " \"id\": \"https://data.discovernewfields.org/37383/production/timespan\",\n", " \"type\": \"TimeSpan\",\n", " \"_label\": \"1887\",\n", " \"begin_of_the_begin\": \"1887\",\n", " \"end_of_the_end\": \"1887\"\n", " },\n", " \"carried_out_by\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/Actor/11566\",\n", " \"type\": \"Actor\",\n", " \"_label\": \"Steele, Theodore Clement\"\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "prod = objProduction(objProp,object_uri)\n", "\n", "if prod is not None:\n", " print(factory.toString(prod, compact=False))" ] }, { "cell_type": "markdown", "id": "0271bb78", "metadata": {}, "source": [ "### Current Owner and Acquisition \n", "\n", "This section is concerned with a representation of the current owner of the artwork and also with an acquisition event.\n", "\n", "\n", "----\n", "##### Linked Art data model\n", "\n", "Acquisitions are used to describe the transfer of ownership of an object from one owner to the next. The first owner is typically the artist, who would then transfer it to the second owner, to the third owner and so on. The ownership chain can be expressed by repeating this same pattern with the buyer from one acquisition being the seller in the subsequent one. If the previous owner (e.g. the seller if there is a value exchange) or the subsequent owner (e.g. the buyer) is not known for a particular acquisition, then the reference can be left out from the description.\n", "\n", "The acquistion is not necessarily a purchase, it could be a gift, an inheritance or any other method of gaining the right of ownership of an object.\n", "\n", "The model encodes this information with an Acquisition part of the overall Provenance Event. The acquisition is the transfer of the right of ownership of an object (referenced in transferred_title_of) from the seller (in transferred_title_from) to the buyer (in transferred_title_to).\n", "\n", "Each object has its own Acquisition as part of the provenance event, so if a collector buys three paintings from a dealer, then there would be a single Provenance Event with three Acquisitions, all of which transfer the title of a single painting from the dealer to the collector.\n", "\n", "-----\n", "\n", "##### IMA data mapping\n", "- The following IMA data fields are used in the representation:\n", " - `accession_date`\n", " - `current_owner`\n", " --`name`, `type`, `type_label`, `name_location`\n", "\n", "----\n", "##### Further reading\n", "\n", "https://linked.art/model/provenance/acquisition/#object-acquisition\n", "\n" ] }, { "cell_type": "code", "execution_count": 25, "id": "552f811d", "metadata": {}, "outputs": [], "source": [ "def objCurrentowner(objProp,object_uri):\n", " current_owner = None\n", " if \"current_owner\" in objProp and objProp[\"current_owner\"][\"name\"] != \"\":\n", " \n", " cowner = objProp[\"current_owner\"]\n", " cowner_name = cowner[\"name\"]\n", " cowner_type = cowner[\"type\"]\n", " cowner_type_label = cowner[\"type_label\"]\n", " current_owner = Group( \"http://vocab.getty.edu/ulan/500300517\",label=cowner_name)\n", " current_owner.classified_as = Type( cowner_type,label=\"museums (institutions)\")\n", " \n", " \n", " acquisition = objAcquisition(objProp,object_uri)\n", " if acquisition is not None:\n", " current_owner.acquired_title_through = acquisition \n", " return current_owner \n", "\n", "def objAcquisition(objProp,object_uri):\n", " acquisition = None\n", " if \"accession_date\" in objProp and objProp[\"accession_date\"] != \"\":\n", " acquisition = Acquisition(object_uri + \"/IMA-acquisition\", label = \"Acquisition of the Object\")\n", " acquisition.classified_as = Type(\"http://vocab.getty.edu/aat/300157782\",\n", " label=\"acquisition (collections management)\")\n", " \n", " if \"name_location\" in objProp[\"current_owner\"]:\n", " acquisition.took_place_at = Place(\"http://vocab.getty.edu/tgn/7012924\", \n", " label=objProp[\"current_owner\"][\"location\"]) \n", " acquisition.timespan = objAcquisitionTimespan(object_uri,objProp[\"accession_date\"])\n", " return acquisition\n", "\n", "def objAcquisitionTimespan(object_uri,accession_date):\n", " timespan = None\n", " end = begin = \"\"\n", " if len(accession_date) == 4:\n", " begin = accession_date + \"-01-01T00:00:00.000Z\"\n", " end = accession_date + \"-12-31T00:00:00.000Z\"\n", " \n", " elif len(accession_date) == 8:\n", " begin = accession_date + \"01T00:00:00.000Z\"\n", " end = accession_date \n", " if '-02-' in accession_date:\n", " end = end + \"28\"\n", " if ('-01-','-03-','-05-','-07-','-08-','-09-','-10-','-12-') in accession_date:\n", " end = end + \"31\"\n", " if ('-04-','-06-','-09-','-11-'):\n", " end = end + \"30\" \n", " end = end + \"T00:00:00\"\n", " \n", " elif len(accession_date) == 10:\n", " begin = accession_date + \"T00:00:00.000Z\"\n", " end = accession_date + \"T00:00:00.000Z\" \n", " else:\n", " begin = end = \"\"\n", " \n", " timespan = TimeSpan(object_uri + \"/IMA-acquisition/timespan\", label=accession_date)\n", " \n", " if begin != \"\":\n", " timespan.begin_of_the_begin = begin\n", " if end != \"\":\n", " timespan.end_of_the_end = end\n", " return timespan" ] }, { "cell_type": "code", "execution_count": 26, "id": "9417588e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"http://vocab.getty.edu/ulan/500300517\",\n", " \"type\": \"Group\",\n", " \"_label\": \"Indianapolis Museum of Art at Newfields\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300312281\",\n", " \"type\": \"Type\",\n", " \"_label\": \"museums (institutions)\"\n", " }\n", " ],\n", " \"acquired_title_through\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/37383/IMA-acquisition\",\n", " \"type\": \"Acquisition\",\n", " \"_label\": \"Acquisition of the Object\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300157782\",\n", " \"type\": \"Type\",\n", " \"_label\": \"acquisition (collections management)\"\n", " }\n", " ],\n", " \"timespan\": {\n", " \"id\": \"https://data.discovernewfields.org/37383/IMA-acquisition/timespan\",\n", " \"type\": \"TimeSpan\",\n", " \"_label\": \"1889\",\n", " \"begin_of_the_begin\": \"1889-01-01T00:00:00.000Z\",\n", " \"end_of_the_end\": \"1889-12-31T00:00:00.000Z\"\n", " }\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "current_owner = objCurrentowner(objProp,object_uri)\n", "\n", "if current_owner is not None:\n", " print(factory.toString(current_owner, compact=False))" ] }, { "cell_type": "markdown", "id": "539cfae1", "metadata": {}, "source": [ "## Custody\n", "This section is concerned with a representation of the custody of an artwork, i.e. the entity responsible for looking after the artwork.\n", "\n", "\n", "-----\n", "#### Linked Art data model\n", "\n", "Objects are owned by legal entities, such as museum organizations or individual people. However there may be more information about which department is responsible within a museum for the curation of the object. This is the division between acquisitions (the legal ownership of the object) and custody (the responsibility for looking after the object). If the department is known, then it should be either part of the Provenance Event in which the object is acquired, or a separate provenance event if the object was not accessioned by a department and later came under their care, or was transferred between departments. In these latter cases, the ownership does not change, only the custody of the object.\n", "\n", "The department becomes the `current_keeper` of the object, whereas the institution is the `current_owner`.\n", "\n", "----\n", "\n", "#### IMA data mapping\n", "IMA data fields used in the representation:\n", "- `current_status`\n", "- `current_owner`\n", "\n", "\n", "----\n", "##### Further reading\n", "\n", "- https://linked.art/model/provenance/custody/#institutional-ownership-departmental-custody\n" ] }, { "cell_type": "code", "execution_count": 27, "id": "9d1c6863", "metadata": {}, "outputs": [], "source": [ "def objCustody(objProp,object_uri):\n", " custody = None\n", " \n", " if \"current_status\" in objProp and objProp[\"current_status\"] != \"\":\n", " current_status = objProp[\"current_status\"]\n", " \n", " current_owner = checkCurrentOwner(current_status)\n", " \n", " if current_owner == False:\n", " name = objProp[\"current_owner\"][\"name\"]\n", " type = objProp[\"current_owner\"][\"type\"]\n", " label = objProp[\"current_owner\"][\"type_label\"]\n", " \n", " custody = Group(label=name)\n", " custody.classified_as = Type(type, \n", " label=label)\n", " return custody\n", "\n", "def checkCurrentOwner(current_status):\n", " current_owner = False\n", " if current_status != \"\":\n", " checkObjStatus = ('Accessioned','Partial Accession')\n", " for status in checkObjStatus:\n", " if status == current_status:\n", " current_owner = True\n", " if \"IMA-Owned\" in current_status:\n", " current_owner = True\n", " return current_owner\n", "\n", "custody = objCustody(objProp,object_uri)\n", "if custody is not None:\n", " print(factory.toString(custody, compact=False))" ] }, { "cell_type": "markdown", "id": "35ce1107", "metadata": {}, "source": [ "### Membership of Collections and Sets \n", "\n", "This section is concerned with the representation of collections or sets that the artwork may belong to.\n", "\n", "----\n", "#### Linked Art data model\n", "\n", "There are many use cases for grouping resources together, often of the same class but sometimes of varying types. These use cases are exemplified in the sections below, and range from the set of objects in an auction lot, to dealer inventories and museum collections, exhibitions, a set of related concepts, or the set of people that share a common feature such as gender or nationality.\n", "\n", "In order to cover all of the use cases with a consistent pattern, we introduce a new `Set` class from outside of CIDOC-CRM. This avoids issues with sets of resources with different types, and the semantics of the identity of objects and collections. If an equivalent class is added into the core CIDOC-CRM ontology in the future, a new major version of the specification will change to using it.\n", "\n", "##### Sets \n", "\n", "Sets are conceptual groupings, rather than physical ones. The set of objects in a virtual exhibition or simply the set of a person's favorite objects never change their physical state by being part of the Set or not. They are, thus, created by a `Creation`, not by a `Production`.\n", "\n", "Like any core resource, Set must have an id and type, are likely to have additional classifications, and can have `Identifiers` and `Names`. They can have statements made about them, and have member resources. These member resources are included via the `member` property rather than `part`, or via `member_of` from the included resource to the `Set`.\n", "\n", "-----\n", "\n", "##### IMA data mapping\n", "The `collection` property in the IMA data is used.\n", "\n", "\n", "\n", "-----\n", "\n", "##### Further reading\n", "https://linked.art/model/collection/\n", "\n" ] }, { "cell_type": "code", "execution_count": 28, "id": "a26a672e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/37383/collection/901.2-American Painting and Sculpture 1800-1945\",\n", " \"type\": \"Set\",\n", " \"_label\": \"901.2-American Painting and Sculpture 1800-1945\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300025976\",\n", " \"type\": \"Type\",\n", " \"_label\": \"collections (object groupings)\"\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "def objCollection(objProp,object_uri):\n", " coll = None\n", " if \"collection\" in objProp:\n", " collection = objProp[\"collection\"]\n", " coll = Set(object_uri +\"/collection/\" + collection, \n", " label= collection)\n", " coll.classified_as = Type(\"http://vocab.getty.edu/aat/300025976\", \n", " label=\"collections (object groupings)\")\n", " return coll\n", "\n", "coll = objCollection(objProp,object_uri)\n", "if coll is not None:\n", " print(factory.toString(coll, compact=False))" ] }, { "cell_type": "markdown", "id": "f54cf605", "metadata": {}, "source": [ "## Create the full Linked Art JSON-LD representation\n", "\n", "Having looked at different representations in the Linked Art data model, this section now brings these together in one Linked Art JSON-LD document.\n", "\n", "The resulting document can be copy+pasted, and visualised in the JSON-LD playground. Alternatively run the code below to visualise the Linked Art JSON-LD document in the notebook.\n", "\n", "-----\n", "#### Further Reading\n", "\n", "- [JSON-LD Playground](https://json-ld.org/playground)\n" ] }, { "cell_type": "code", "execution_count": 29, "id": "d2f4e682", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://data.discovernewfields.org/37383\",\n", " \"type\": \"HumanMadeObject\",\n", " \"_label\": \"Oaks of Vernon\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300033618\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Painting\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300435443\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Type of Work\"\n", " }\n", " ]\n", " }\n", " ],\n", " \"identified_by\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/Identifier/89.1\",\n", " \"type\": \"Identifier\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300312355\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Accession Number\"\n", " }\n", " ],\n", " \"content\": \"89.1\"\n", " },\n", " {\n", " \"id\": \"https://data.discovernewfields.org/Identifier/37383\",\n", " \"type\": \"Identifier\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300404621\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Owner-Assigned Number\"\n", " }\n", " ],\n", " \"content\": \"37383\"\n", " }\n", " ],\n", " \"referred_to_by\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/37383/provenance-statement\",\n", " \"type\": \"LinguisticObject\",\n", " \"_label\": \"Provenance Statement about the Object\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300055863\",\n", " \"type\": \"Type\",\n", " \"_label\": \"provenance (history of ownership)\"\n", " },\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300418049\",\n", " \"type\": \"Type\",\n", " \"_label\": \"brief texts\"\n", " }\n", " ],\n", " \"content\": \"Purchased from the artist by the John Herron Art Institute, Indianapolis, Indiana, now the Indianapolis Museum of Art in 1889.\"\n", " }\n", " ],\n", " \"member_of\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/37383/collection/901.2-American Painting and Sculpture 1800-1945\",\n", " \"type\": \"Set\",\n", " \"_label\": \"901.2-American Painting and Sculpture 1800-1945\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300025976\",\n", " \"type\": \"Type\",\n", " \"_label\": \"collections (object groupings)\"\n", " }\n", " ]\n", " }\n", " ],\n", " \"subject_of\": [\n", " {\n", " \"id\": \"http://collection.imamuseum.org/artwork/37383\",\n", " \"type\": \"LinguisticObject\",\n", " \"_label\": \"Homepage for the Object\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab/getty.edu/aat/300264578\",\n", " \"type\": \"Type\",\n", " \"_label\": \"Web pages (documents)\"\n", " },\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300266277\",\n", " \"type\": \"Type\",\n", " \"_label\": \"home pages\"\n", " }\n", " ],\n", " \"format\": \"text/html\"\n", " }\n", " ],\n", " \"produced_by\": {\n", " \"id\": \"https://data.discovernewfields.org/37383/production\",\n", " \"type\": \"Production\",\n", " \"_label\": \"Production of the Object\",\n", " \"timespan\": {\n", " \"id\": \"https://data.discovernewfields.org/37383/production/timespan\",\n", " \"type\": \"TimeSpan\",\n", " \"_label\": \"1887\",\n", " \"begin_of_the_begin\": \"1887\",\n", " \"end_of_the_end\": \"1887\"\n", " },\n", " \"carried_out_by\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/Actor/11566\",\n", " \"type\": \"Actor\",\n", " \"_label\": \"Steele, Theodore Clement\"\n", " }\n", " ]\n", " },\n", " \"current_owner\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/ulan/500300517\",\n", " \"type\": \"Group\",\n", " \"_label\": \"Indianapolis Museum of Art at Newfields\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300312281\",\n", " \"type\": \"Type\",\n", " \"_label\": \"museums (institutions)\"\n", " }\n", " ],\n", " \"acquired_title_through\": [\n", " {\n", " \"id\": \"https://data.discovernewfields.org/37383/IMA-acquisition\",\n", " \"type\": \"Acquisition\",\n", " \"_label\": \"Acquisition of the Object\",\n", " \"classified_as\": [\n", " {\n", " \"id\": \"http://vocab.getty.edu/aat/300157782\",\n", " \"type\": \"Type\",\n", " \"_label\": \"acquisition (collections management)\"\n", " }\n", " ],\n", " \"timespan\": {\n", " \"id\": \"https://data.discovernewfields.org/37383/IMA-acquisition/timespan\",\n", " \"type\": \"TimeSpan\",\n", " \"_label\": \"1889\",\n", " \"begin_of_the_begin\": \"1889-01-01T00:00:00.000Z\",\n", " \"end_of_the_end\": \"1889-12-31T00:00:00.000Z\"\n", " }\n", " }\n", " ]\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "def createObjDesc(objProp,objTypes,object_uri):\n", " objLA = None\n", " objLA = HumanMadeObject() # linked art object\n", "\n", " for otype in objTypes:\n", " if otype in objProp[\"classification\"]:\n", " objLA = objTypes[otype] \n", " break\n", " \n", " objLA.id = object_uri\n", " objLA._label = objProp[\"title\"]\n", " \n", " # IDENTIFIED_BY \n", " accession = objAccession(objProp,object_uri)\n", " localnumber = objLocalnumber(objProp,object_uri)\n", " #primaryname = objPrimaryname(objProp,object_uri)\n", " \n", " # listIds = (accession,localnumber,primaryname)\n", "\n", " listIds = (accession,localnumber)\n", " identified_by = False\n", " for id in listIds:\n", " if id is not None:\n", " identified_by = True\n", " break\n", " if identified_by == True:\n", " objLA.identified_by = []\n", " \n", " for id in listIds:\n", " if id is not None:\n", " objLA.identified_by.append(id)\n", " \n", " # REFERRED_TO_BY\n", " objLA.referred_to_by = None\n", " prov = objProvenance(objProp,object_uri)\n", " credit = objCredit(objProp,object_uri)\n", " referred_to_by = False\n", " if prov is not None or credit is not None:\n", " referred_to_by = True\n", " if referred_to_by == True:\n", " objLA.referred_to_by = []\n", " if prov is not None:\n", " objLA.referred_to_by.append(prov) # provenance \n", " if credit is not None:\n", " objLA.referred_to_by.append(credit) # credit line\n", " \n", " # SUBJECT_OF \n", " objLA.subject_of = None \n", " homepage = None \n", " homepage = objHomepage(objProp,object_uri)\n", " if homepage is not None:\n", " objLA.subject_of = homepage # home page\n", " \n", " # PRODUCED_BY\n", " objLA.produced_by = None \n", " if \"creator\" in objProp:\n", " prod = None\n", " prod = objProduction(objProp,object_uri)\n", " objLA.produced_by = None\n", " if prod is not None:\n", " objLA.produced_by = prod # production\n", "\n", " # MEMBER_OF\n", " objLA.member_of = None \n", " if \"collection\" in objProp:\n", " coll = None\n", " coll = objCollection(objProp,object_uri)\n", " if coll is not None:\n", " objLA.member_of = coll # collection\n", "\n", " # CURRENT_KEEPER\n", " objLA.current_owner = None \n", " custody = None\n", " custody = objCustody(objProp,object_uri)\n", " if custody is not None:\n", " objLA.current_owner = custody\n", " \n", " # CURRENT_OWNER\n", " if \"current_owner\" in objProp and objProp[\"current_owner\"] != \"\":\n", " current_owner = objCurrentowner(objProp,object_uri)\n", " if current_owner is not None:\n", " objLA.current_owner = current_owner\n", " \n", " return objLA\n", "\n", "objProp = createObjProp(obj,mapp)\n", "ObjLA = None\n", "objLA = createObjDesc(objProp,objTypes,object_uri)\n", "data = factory.toString(objLA, compact=False)\n", "\n", "f = open(\"./data/ima/output/json/object_linkedart.json\", \"w\")\n", "f.write(data)\n", "f.close() \n", "print(data)\n" ] }, { "cell_type": "markdown", "id": "b7efee8c", "metadata": {}, "source": [ "## View the Linked Art JSON-LD\n", "\n", "The final Linked Art JSON-LD representation of the object is visualised below." ] }, { "cell_type": "code", "execution_count": 30, "id": "c33483e6", "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "\n", "require.config({paths: {\n", " d3: \"http://d3js.org/d3.v3.min\"\n", "}});\n", "\n", "function visjsonld(file, selector){\n", " \n", " require([\"d3\"], function(d3) {\n", " \n", " d3.json(file, (err, jsonld) => {\n", " \n", " var config = {};\n", " \n", " var h = config.h || 800\n", " , w = config.w || 3000\n", " , maxLabelWidth = config.maxLabelWidth || 200\n", " , transitionDuration = config.transitionDuration || 750\n", " , transitionEase = config.transitionEase || 'cubic-in-out'\n", " , minRadius = config.minRadius || 5\n", " , scalingFactor = config.scalingFactor || 2;\n", " \n", " var i = 0;\n", "\n", " var tree = d3.layout.tree()\n", " .size([h, w]);\n", " \n", " var diagonal = d3.svg.diagonal()\n", " .projection(function(d) { return [d.y, d.x]; });\n", " \n", " d3.select(selector).selectAll(\"svg\").remove();\n", " \n", " var svg = d3.select(selector).append('svg')\n", " .attr('width', w)\n", " .attr('height', h)\n", " .attr('style', 'background-color:#446a7f')\n", " .append('g')\n", " .attr('transform', 'translate(' + maxLabelWidth + ',0)');\n", " \n", " var root = jsonldTree(jsonld);\n", " root.x0 = h / 2;\n", " root.y0 = 0;\n", " root.children.forEach(collapse);\n", " \n", " function changeSVGWidth(newWidth) {\n", " if (w !== newWidth) {\n", " d3.select(selector + ' > svg').attr('width', newWidth);\n", " }\n", " }\n", " \n", " function jsonldTree(source) {\n", " var tree = {};\n", " \n", " if ('@id' in source) {\n", " tree.isIdNode = true;\n", " tree.name = source['@id'];\n", " if (tree.name.length > maxLabelWidth / 9) {\n", " tree.valueExtended = tree.name;\n", " tree.name = '...' + tree.valueExtended.slice(-Math.floor(maxLabelWidth / 9));\n", " }\n", " } else {\n", " tree.isIdNode = true;\n", " tree.isBlankNode = true;\n", " // random id, can replace with actual uuid generator if needed\n", " tree.name = '_' + Math.random().toString(10).slice(-7);\n", " }\n", " \n", " var children = [];\n", " Object.keys(source).forEach(function(key) {\n", " if (key === '@id' || key === '@context' || source[key] === null) return;\n", " \n", " var valueExtended, value;\n", " if (typeof source[key] === 'object' && !Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: [jsonldTree(source[key])]\n", " });\n", " } else if (Array.isArray(source[key])) {\n", " children.push({\n", " name: key,\n", " children: source[key].map(function(item) {\n", " if (typeof item === 'object') {\n", " return jsonldTree(item);\n", " } else {\n", " return { name: item };\n", " }\n", " })\n", " });\n", " } else {\n", " valueExtended = source[key];\n", " value = valueExtended;\n", " if (value.length > maxLabelWidth / 9) {\n", " value = value.slice(0, Math.floor(maxLabelWidth / 2)) + '...';\n", " children.push({\n", " name: key,\n", " value: value,\n", " valueExtended: valueExtended\n", " });\n", " } else {\n", " children.push({\n", " name: key,\n", " value: value\n", " });\n", " }\n", " }\n", " });\n", " \n", " if (children.length) {\n", " tree.children = children;\n", " }\n", " \n", " return tree;\n", " }\n", " \n", " function update(source) {\n", " var nodes = tree.nodes(root).reverse();\n", " var links = tree.links(nodes);\n", " \n", " nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; });\n", " \n", " var node = svg.selectAll('g.node')\n", " .data(nodes, function(d) { return d.id || (d.id = ++i); });\n", " \n", " var nodeEnter = node.enter()\n", " .append('g')\n", " .attr('class', 'node')\n", " .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; })\n", " .on('click', click);\n", " \n", " nodeEnter.append('circle')\n", " .attr('r', 0)\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? 'navy' : '#78BE21';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? 'white' : 'white';\n", " } else {\n", " return d._children ? 'white' : 'white';\n", " }\n", " })\n", " \n", " \n", " nodeEnter.append('text')\n", " .attr('x', function(d) {\n", " var spacing = computeRadius(d) + 5;\n", " return d.children || d._children ? -spacing : spacing;\n", " })\n", " .attr('dy', '4')\n", " .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })\n", " .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })\n", " .style('fill-opacity', 0)\n", " \n", ";\n", " \n", " var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; }));\n", " if (maxSpan + maxLabelWidth + 20 > w) {\n", " changeSVGWidth(maxSpan + maxLabelWidth);\n", " d3.select(selector).node().scrollLeft = source.y0;\n", " }\n", " \n", " var nodeUpdate = node.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });\n", " \n", " nodeUpdate.select('circle')\n", " .attr('r', function(d) { return computeRadius(d); })\n", " .style('stroke-width', function(d) {\n", " return d.isIdNode ? '2px' : '1px';\n", " })\n", " .style('stroke', function(d) {\n", " return d.isIdNode ? '#78BE21' : '#78BE21';\n", " })\n", " .style('fill', function(d) {\n", " if (d.isIdNode) {\n", " return d._children ? 'navy' : '#78BE21';\n", " } else {\n", " return d._children ? 'navy' : '#78BE21';\n", " }\n", " });\n", " \n", " nodeUpdate.select('text').style('fill-opacity', 1);\n", " nodeUpdate.select('text').style('font-family', \"'Open Sans', 'Helvetica Neue', Helvetica, sans-serif\");\n", " nodeUpdate.select('text').style('fill', 'white');\n", " nodeUpdate.select('text').style('font-size', '12px');\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " var nodeExit = node.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })\n", " .remove();\n", " \n", " nodeExit.select('circle').attr('r', 0);\n", " nodeExit.select('text').style('fill-opacity', 0);\n", " \n", " var link = svg.selectAll('path.link')\n", " .data(links, function(d) { return d.target.id; });\n", " \n", " link.enter().insert('path', 'g')\n", " .attr('class', 'link')\n", " .attr('style', 'fill: none;stroke: #DADFE1;stroke-width: 1px;')\n", " .attr('d', function(d) {\n", " var o = { x: source.x0, y: source.y0 };\n", " return diagonal({ source: o, target: o });\n", " });\n", " \n", " link.transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', diagonal);\n", " \n", " link.exit().transition()\n", " .duration(transitionDuration)\n", " .ease(transitionEase)\n", " .attr('d', function(d) {\n", " var o = { x: source.x, y: source.y };\n", " return diagonal({ source: o, target: o });\n", " })\n", " .remove();\n", " \n", " nodes.forEach(function(d) {\n", " d.x0 = d.x;\n", " d.y0 = d.y;\n", " });\n", " }\n", " \n", " function computeRadius(d) {\n", " if (d.children || d._children) {\n", " return minRadius + (numEndNodes(d) / scalingFactor);\n", " } else {\n", " return minRadius;\n", " }\n", " }\n", " \n", " function numEndNodes(n) {\n", " var num = 0;\n", " if (n.children) {\n", " n.children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else if (n._children) {\n", " n._children.forEach(function(c) {\n", " num += numEndNodes(c);\n", " });\n", " } else {\n", " num++;\n", " }\n", " return num;\n", " }\n", " \n", " function click(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d.children = null;\n", " } else {\n", " d.children = d._children;\n", " d._children = null;\n", " }\n", " \n", " update(d);\n", " \n", " // fast-forward blank nodes\n", " if (d.children) {\n", " d.children.forEach(function(child) {\n", " if (child.isBlankNode && child._children) {\n", " click(child);\n", " }\n", " });\n", " }\n", " }\n", " \n", " function collapse(d) {\n", " if (d.children) {\n", " d._children = d.children;\n", " d._children.forEach(collapse);\n", " d.children = null;\n", " }\n", " }\n", " \n", " update(root);\n", " \n", " \n", " })\n", "\n", "\n", "\n", "\n", "})}\n", "var file = './data/ima/output/json/object_linkedart.json';var selector = '#vis3';visjsonld(file, selector); " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import display, Javascript\n", "\n", "with open('./src/js/visld.js', 'r') as _jscript:\n", " code = _jscript.read() + \"var file = './data/ima/output/json/object_linkedart.json';var selector = '#vis3';visjsonld(file, selector); \"\n", " display(Javascript(code))" ] }, { "cell_type": "markdown", "id": "eaa9706b", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": null, "id": "f9de2649", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "interpreter": { "hash": "40d3a090f54c6569ab1632332b64b2c03c39dcf918b08424e98f38b5ae0af88f" }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 5 }