|
| 1 | +--- |
| 2 | +title: Virtual properties |
| 3 | +description: Configure virtual properties in HERITRACE to align the user interface with user mental models while maintaining ontological correctness in the underlying RDF data. |
| 4 | +--- |
| 5 | +import { Aside, Code } from '@astrojs/starlight/components'; |
| 6 | + |
| 7 | +Virtual properties allow you to create properties that appear in the user interface but don't exist directly as predicates in your RDF graph. They bridge the gap between how data is ontologically modeled (often as classes) and how users naturally think about it (typically as relationships), making the interface more intuitive without compromising the integrity of your data model. |
| 8 | + |
| 9 | +## What are virtual properties? |
| 10 | + |
| 11 | +A virtual property is a user-facing field that, when populated, automatically creates one or more intermediate entities in the knowledge graph. From the user's perspective, they're adding a simple relationship. Behind the scenes, HERITRACE creates the necessary RDF structure to represent it correctly. |
| 12 | + |
| 13 | +<Aside type="note" title="Virtual vs. Regular Properties"> |
| 14 | +**Regular properties** directly correspond to RDF predicates in your data model. When you add a value, a triple is created. |
| 15 | + |
| 16 | +**Virtual properties** have no direct RDF predicate. When you add a value, HERITRACE creates intermediate entities with specific properties based on your configuration. |
| 17 | +</Aside> |
| 18 | + |
| 19 | +## Why use virtual properties? |
| 20 | + |
| 21 | +The primary purpose of virtual properties is to **bridge the gap between ontological representation and user mental models**. |
| 22 | + |
| 23 | +In RDF ontologies, certain concepts are correctly modeled as classes (first-class entities) because they carry important metadata and enable complex relationships. However, domain experts often think of these same concepts as simple relationships or attributes. This mismatch creates cognitive friction and makes the interface harder to use. |
| 24 | + |
| 25 | +For example, in the CITO ontology, citations are modeled as a `cito:Citation` class—an entity that links two bibliographic resources. This is ontologically correct: citations can have types, contexts, and other metadata. But when users edit an article, they think "I want to cite this paper," not "I want to create a Citation entity that links my article to that paper." |
| 26 | + |
| 27 | +Virtual properties resolve this tension by: |
| 28 | + |
| 29 | +- **Presenting a simplified view**: Users interact with what appears to be a simple relationship field |
| 30 | +- **Managing complexity automatically**: HERITRACE creates and configures the intermediate entities behind the scenes |
| 31 | +- **Maintaining data model integrity**: The underlying RDF structure remains correct and compliant with the ontology |
| 32 | +- **Supporting bidirectional navigation**: Both directions of a relationship can be shown (e.g., "Citations" and "Is Cited By") even though they're backed by the same entities in the graph |
| 33 | + |
| 34 | +Use virtual properties when your ontology models something as a class, but users naturally think of it as a relationship or when you need to hide implementation details from domain experts who shouldn't need to understand the full ontological structure. |
| 35 | + |
| 36 | +## Configuration structure |
| 37 | + |
| 38 | +Virtual properties are configured within the `displayProperties` section of a display rule, using several specific keys. |
| 39 | + |
| 40 | +### Basic structure |
| 41 | + |
| 42 | +```yaml |
| 43 | +displayProperties: |
| 44 | + - displayName: "Property Label" |
| 45 | + isVirtual: true |
| 46 | + shouldBeDisplayed: true |
| 47 | + fetchValueFromQuery: | |
| 48 | + # SPARQL query to fetch and format values |
| 49 | + implementedVia: |
| 50 | + target: |
| 51 | + class: "http://example.org/IntermediateClass" |
| 52 | + shape: "http://example.org/IntermediateShape" |
| 53 | + fieldOverrides: |
| 54 | + "http://example.org/property1": |
| 55 | + shouldBeDisplayed: false |
| 56 | + value: "${currentEntity}" |
| 57 | + "http://example.org/property2": |
| 58 | + displayName: "User-Facing Label" |
| 59 | +``` |
| 60 | +
|
| 61 | +### Configuration keys |
| 62 | +
|
| 63 | +- **`isVirtual`**: Must be set to `true` to enable virtual property behavior |
| 64 | +- **`displayName`**: The label shown in the user interface for this property |
| 65 | +- **`shouldBeDisplayed`**: Controls whether this property appears in forms and entity views |
| 66 | +- **`fetchValueFromQuery`**: A SPARQL query that retrieves and formats the display value. The query context is the intermediate entity, referenced by `[[value]]` |
| 67 | +- **`implementedVia`**: Defines how the virtual property is implemented in the RDF graph |
| 68 | + |
| 69 | +#### The `implementedVia` section |
| 70 | + |
| 71 | +This section tells HERITRACE what kind of intermediate entity to create and how to configure its properties. |
| 72 | + |
| 73 | +**`target`**: Specifies the type of intermediate entity |
| 74 | +- `class`: The RDF class (`rdf:type`) for the intermediate entity |
| 75 | +- `shape`: The SHACL shape the intermediate entity conforms to |
| 76 | + |
| 77 | +**`fieldOverrides`**: A mapping of property URIs to their configuration |
| 78 | +- Each key is a property URI on the intermediate entity |
| 79 | +- Each value is an object that can contain: |
| 80 | + - `shouldBeDisplayed`: If `false`, the field is hidden from the user and automatically populated |
| 81 | + - `value`: The value to set automatically. Can use the special placeholder `${currentEntity}` to reference the URI of the entity being edited |
| 82 | + - `displayName`: A custom label for fields that are shown to the user |
| 83 | + |
| 84 | +## Complete example: Bidirectional citations |
| 85 | + |
| 86 | +This example shows how to configure virtual properties for a citation system where citations can be viewed from both directions. |
| 87 | + |
| 88 | +### The scenario |
| 89 | + |
| 90 | +You have bibliographic resources that cite each other. In your RDF model, a `Citation` entity represents each citation, linking the citing resource to the cited resource. You want users to see: |
| 91 | + |
| 92 | +- On the citing resource: A "Citations" property showing what it cites |
| 93 | +- On the cited resource: An "Is Cited By" property showing what cites it |
| 94 | + |
| 95 | +But you only want to store one `Citation` entity in the graph, not duplicate data. |
| 96 | + |
| 97 | +### The data model |
| 98 | + |
| 99 | +```turtle |
| 100 | +@prefix cito: <http://purl.org/spar/cito/> . |
| 101 | +@prefix : <http://example.org/> . |
| 102 | +
|
| 103 | +# A citation entity links the two resources |
| 104 | +:citation1 a cito:Citation ; |
| 105 | + cito:hasCitingEntity :articleA ; |
| 106 | + cito:hasCitedEntity :articleB ; |
| 107 | + cito:hasCitationCharacterization cito:cites . |
| 108 | +``` |
| 109 | + |
| 110 | +In this model: |
| 111 | +- `:articleA` is the resource that cites |
| 112 | +- `:articleB` is the resource being cited |
| 113 | +- The `Citation` entity holds the relationship and can describe it (e.g., the type of citation) |
| 114 | + |
| 115 | +### Configuring the "Citations" property |
| 116 | + |
| 117 | +This virtual property appears on the citing resource and shows what it cites. |
| 118 | + |
| 119 | +```yaml |
| 120 | +# First, define the query as a reusable anchor |
| 121 | +queries: |
| 122 | + citations_query: &citations_query | |
| 123 | + PREFIX cito: <http://purl.org/spar/cito/> |
| 124 | + PREFIX dcterms: <http://purl.org/dc/terms/> |
| 125 | + SELECT ?display ?target WHERE { |
| 126 | + [[value]] a cito:Citation ; |
| 127 | + cito:hasCitingEntity [[subject]] ; |
| 128 | + cito:hasCitedEntity ?target . |
| 129 | + ?target dcterms:title ?title . |
| 130 | + BIND(CONCAT(?title, " (", STR(?target), ")") AS ?display) |
| 131 | + } |
| 132 | +
|
| 133 | +# Then use it in a virtual property |
| 134 | +virtual_properties: |
| 135 | + citations_property: &citations_property |
| 136 | + displayName: "Citations" |
| 137 | + isVirtual: true |
| 138 | + shouldBeDisplayed: true |
| 139 | + fetchValueFromQuery: *citations_query |
| 140 | + implementedVia: |
| 141 | + target: |
| 142 | + class: "http://purl.org/spar/cito/Citation" |
| 143 | + shape: "http://schema.org/CitationShape" |
| 144 | + fieldOverrides: |
| 145 | + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": |
| 146 | + shouldBeDisplayed: false |
| 147 | + value: "http://purl.org/spar/cito/Citation" |
| 148 | + "http://purl.org/spar/cito/hasCitingEntity": |
| 149 | + shouldBeDisplayed: false |
| 150 | + value: "${currentEntity}" |
| 151 | + "http://purl.org/spar/cito/hasCitedEntity": |
| 152 | + displayName: "Cited Resource" |
| 153 | + "http://purl.org/spar/cito/hasCitationCharacterization": |
| 154 | + displayName: "Citation Type" |
| 155 | +``` |
| 156 | + |
| 157 | +**What happens when a user adds a citation:** |
| 158 | + |
| 159 | +1. The user sees a "Citations" field and a form to add a cited resource |
| 160 | +2. The `rdf:type` field is hidden and automatically set to `cito:Citation` |
| 161 | +3. The `cito:hasCitingEntity` field is hidden and automatically set to the current article's URI |
| 162 | +4. The user selects the cited resource in the `cito:hasCitedEntity` field (labeled "Cited Resource") |
| 163 | +5. The user optionally selects a citation type in the `cito:hasCitationCharacterization` field |
| 164 | +6. HERITRACE creates a new `Citation` entity with all these properties |
| 165 | + |
| 166 | +### Configuring the "Is Cited By" property |
| 167 | + |
| 168 | +This virtual property appears on the cited resource and shows what cites it. It queries for `Citation` entities where the current resource is the cited entity. |
| 169 | + |
| 170 | +```yaml |
| 171 | +# Define the inverse query |
| 172 | +queries: |
| 173 | + citing_entity_query: &citing_entity_query | |
| 174 | + PREFIX cito: <http://purl.org/spar/cito/> |
| 175 | + PREFIX dcterms: <http://purl.org/dc/terms/> |
| 176 | + SELECT ?display ?target WHERE { |
| 177 | + [[value]] cito:hasCitingEntity ?target . |
| 178 | + ?target dcterms:title ?title . |
| 179 | + BIND(CONCAT(?title, " (", STR(?target), ")") AS ?display) |
| 180 | + } |
| 181 | +
|
| 182 | +# Configure the reverse virtual property |
| 183 | +virtual_properties: |
| 184 | + is_cited_by_property: &is_cited_by_property |
| 185 | + displayName: "Is Cited By" |
| 186 | + isVirtual: true |
| 187 | + shouldBeDisplayed: true |
| 188 | + fetchValueFromQuery: *citing_entity_query |
| 189 | + implementedVia: |
| 190 | + target: |
| 191 | + class: "http://purl.org/spar/cito/Citation" |
| 192 | + shape: "http://schema.org/ReverseCitationShape" |
| 193 | + fieldOverrides: |
| 194 | + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": |
| 195 | + shouldBeDisplayed: false |
| 196 | + value: "http://purl.org/spar/cito/Citation" |
| 197 | + "http://purl.org/spar/cito/hasCitedEntity": |
| 198 | + shouldBeDisplayed: false |
| 199 | + value: "${currentEntity}" |
| 200 | + "http://purl.org/spar/cito/hasCitingEntity": |
| 201 | + displayName: "Citing Resource" |
| 202 | + "http://purl.org/spar/cito/hasCitationCharacterization": |
| 203 | + displayName: "Citation Type" |
| 204 | +``` |
| 205 | + |
| 206 | +**Key differences from the forward citation:** |
| 207 | +- The `fetchValueFromQuery` retrieves the citing entity instead of the cited entity |
| 208 | +- `hasCitedEntity` is now automatically set to `${currentEntity}` (instead of `hasCitingEntity`) |
| 209 | +- `hasCitingEntity` is now the user-facing field (instead of `hasCitedEntity`) |
| 210 | +- A different SHACL shape (`ReverseCitationShape`) distinguishes it from forward citations |
| 211 | + |
| 212 | +### Using the virtual properties in display rules |
| 213 | + |
| 214 | +Once defined, you use the virtual properties in your entity rules: |
| 215 | + |
| 216 | +```yaml |
| 217 | +rules: |
| 218 | + - target: |
| 219 | + class: "http://purl.org/spar/fabio/JournalArticle" |
| 220 | + displayName: "Journal Article" |
| 221 | + displayProperties: |
| 222 | + # ... other properties ... |
| 223 | + - *citations_property |
| 224 | + - *is_cited_by_property |
| 225 | +``` |
| 226 | + |
| 227 | +Now, when a user views or edits a journal article, they see both "Citations" and "Is Cited By" fields, even though the underlying data is stored as `Citation` entities. |
| 228 | + |
| 229 | +## The `${currentEntity}` placeholder |
| 230 | + |
| 231 | +The special `${currentEntity}` placeholder is replaced with the URI of the entity currently being created or edited. This is essential for linking the intermediate entity back to the main entity. |
| 232 | + |
| 233 | +**Example:** |
| 234 | +```yaml |
| 235 | +fieldOverrides: |
| 236 | + "http://example.org/linksBackTo": |
| 237 | + shouldBeDisplayed: false |
| 238 | + value: "${currentEntity}" |
| 239 | +``` |
| 240 | + |
| 241 | +When creating a citation from Article A's page, `${currentEntity}` becomes Article A's URI. This ensures the `Citation` entity correctly references Article A. |
| 242 | + |
| 243 | +## Understanding `fetchValueFromQuery` context |
| 244 | + |
| 245 | +For virtual properties, the `fetchValueFromQuery` has a specific context: |
| 246 | + |
| 247 | +- `[[subject]]`: The URI of the main entity being viewed (e.g., the article) |
| 248 | +- `[[value]]`: The URI of the intermediate entity (e.g., the `Citation` entity) |
| 249 | + |
| 250 | +The query starts from the intermediate entity and navigates to retrieve the display value. |
| 251 | + |
| 252 | +**Example:** |
| 253 | +```sparql |
| 254 | +SELECT ?display ?target WHERE { |
| 255 | + # [[value]] is the Citation entity URI |
| 256 | + [[value]] cito:hasCitedEntity ?target . |
| 257 | +
|
| 258 | + # Navigate from the Citation to get the cited resource's title |
| 259 | + ?target dcterms:title ?title . |
| 260 | +
|
| 261 | + # Format the display string |
| 262 | + BIND(CONCAT(?title, " (citation)") AS ?display) |
| 263 | +} |
| 264 | +``` |
| 265 | + |
| 266 | +The query must return two variables in order: |
| 267 | +1. The formatted display string |
| 268 | +2. The URI of the final target entity |
0 commit comments