Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rdfs:subPropertyOf as hierarchy in widget #704

Open
ch-sander opened this issue Jan 21, 2025 · 17 comments
Open

rdfs:subPropertyOf as hierarchy in widget #704

ch-sander opened this issue Jan 21, 2025 · 17 comments

Comments

@ch-sander
Copy link

while the widget super nicely reflects rdfs:subClassOf as nested selector, why not also allow this for rdfs:subPropertyOf if specified in SHACL? Maybe the same horizontal design can be used in UI. For my data the hierarchy of properties is even more important than for classes.

@tfrancart
Copy link
Contributor

while the widget super nicely reflects rdfs:subClassOf as nested selector, why not also allow this for rdfs:subPropertyOf if specified in SHACL? Maybe the same horizontal design can be used in UI. For my data the hierarchy of properties is even more important than for classes.

Well, it works already this way. See https://blog.sparna.fr/2025/01/21/sparnatural-shacl-configuration-manual-automated-off-the-shelf/ and in particular https://blog.sparna.fr/wp-content/uploads/2025/01/sparnatural-hierarchy.gif.
Is there anything you see (or don't see) that makes you think it doesn't work ?

@ch-sander
Copy link
Author

Ah, my bad! Thanks! In this example, where would the rdfs:subPropertyOf be specified?

Its not in https://www.nakala.fr/sparnatural/nakala-shacl-filtered.ttl nor in https://www.nakala.fr/sparnatural/nakala-statistics.ttl, right? It might feature in the separate OWL file, but I assume it could also be in the SHACL (if shapes are classes), right?

void:distinctObjects "2242"^^xsd:int ; will add the smart numbers of objects to appear on hovering a class?

@tfrancart
Copy link
Contributor

In this example, where would the rdfs:subPropertyOf be specified?

It is in the DublinCore ontology (dcterms_2012-06-14.n3). The DublinCore ontology is passed along with the SHACL specs to Sparnatural:

(from https://www.nakala.fr/sparnatural/)

<spar-natural
            src="nakala-shacl-filtered.ttl nakala-statistics.ttl edm.owl.rdf ore-terms.rdf dcterms_2012-06-14.n3 nakala-labels.ttl"
            endpoint="https://nakala.fr/sparql" lang="fr" defaultLang="fr" distinct="true" limit="1000"
            debug="true"></spar-natural>

(see how multiple files are given in the src attribute)

Nothing prevents you from putting the rdfs:subPropertyOf in the SHACL. But they better fit in a separate OWL file;

void:distinctObjects "2242"^^xsd:int ; will add the smart numbers of objects to appear on hovering a class?

That's a little more elaborated. What you need is a statistics file like this one: https://shacl-play.sparna.fr/play/generate#statistics-model
You can generate this statistics file automatically from your SHACL spec + your RDF dataset from the SHACL Play analysis tool : https://github.com/sparna-git/shacl-play/wiki/Run-SHACL-Play-App-from-command-line#analyze
Note that the void:distinctObjects statistics, if present, will enable Sparnatural to choose automatically between a dropdown list (if < 500) and an autocomplete field (if >= 500), if not explicitely provided in the config.

@ch-sander
Copy link
Author

Wonderful! I'll try to implement all of this and get back to you, if stuck ;)

@ch-sander
Copy link
Author

SHACL:

g17sn:apostolic_dispensation a rdfs:Class,
        sh:NodeShape ;
    rdfs:label "apostolic dispensation"@en ;
    volipi:iconName "fa-solid fa-gavel" ;
    sh:name "apostolic dispensation" ;
    sh:nodeKind sh:IRI ;
    sh:order 2.0 ;
    sh:targetClass grace:apostolic_dispensation .

g17sn:apostolic_provision a rdfs:Class,
        sh:NodeShape ;
    rdfs:label "apostolic provision"@en ;
    volipi:iconName "fa-solid fa-gavel" ;
    sh:name "apostolic provision" ;
    sh:nodeKind sh:IRI ;
    sh:order 2.0 ;
    sh:targetClass grace:apostolic_provision .

OWL

grace:apostolic_provision a owl:Class ;
    rdfs:label "apostolic provision"@en ;
    rdfs:subClassOf grace:papal_grace .

grace:apostolic_dispensation a owl:Class ;
    rdfs:label "apostolic dispensation"@en ;
    rdfs:subClassOf grace:papal_grace .

I put both configs to load into Sparnatural, but won't work like that. If I stick to something like rdfs:subClassOf g17sn:papal_grace in the SHACL, it works. Seems to be a major misunderstanding of mine.

@tfrancart
Copy link
Contributor

I suspect this can come from the fact that your NodeShape are also types with rdfs:Class.
If your NodeShapes have both types rdfs:Class and sh:NodeShape, it implicitely means that their target are the (indirect) instances of their own URI (as per https://www.w3.org/TR/shacl/#implicit-targetClass). So you wouldn't need an sh:targetClass in this case. I suspect that you should remove the a rdfs:Class triple from your SHACL spec.

Either:

  • your NodeShapes are also classes, and they don't have an sh:targetClass and you link them directly with rdfs:subClassOf
  • OR your NodeShapes have a sh:targetClass, they are not classes themselves, and it is the class that they target that are linked with rdfs:subClassOf

@ch-sander
Copy link
Author

I suspect this can come from the fact that your NodeShape are also types with rdfs:Class.

Correct, that's the cause!

@ch-sander
Copy link
Author

I used sh:targetClass grace:apostolic_provision etc. for the nodeShapes so that the SPARQL is built using the grace classes (e.g. grace:apostolic_provision), not the g17sn classes (i.e. the SHACL, e.g. g17sn:apostolic_provision). I think I wanted to have only one config as src of app, I could solve it only this way. Maybe I should load both SHACL and OWL and remove rdfs:Class from shapes.

Otherwise I get a mix that is not well formed (see rdf:type <https://g17.dhi-roma.it/shacl/sparnatural-config/event):

SELECT DISTINCT ?event_1 ?event_1_label WHERE {
  ?event_1 rdf:type <https://g17.dhi-roma.it/shacl/sparnatural-config/event>;
    <https://g17.dhi-roma.it/ontology/called> ?event_1_label;
    ((<https://g17.dhi-roma.it/ontology/object_object>*)|^(<https://g17.dhi-roma.it/ontology/event_object>*)) ?object_2.
  ?object_2 rdf:type <https://g17.dhi-roma.it/ontology/object>.
}

@ch-sander
Copy link
Author

For properties, it doesn't work like it does for classes with hierarchies in SHACL:

g17sn:biography_event a rdfs:Property,
        sh:PropertyShape ;
    rdfs:label "biography event"@en ;
    volipi:message "Tooltip for biography event" ;
    dash:searchWidget sparnatural:ListProperty ;
    rdfs:subPropertyOf g17sn:participates_in ;
    sh:class grace:biographical_event ;
    sh:name "experienced" ;
    sh:node g17sn:biographical_event ;
    sh:nodeKind sh:IRI ;
    sh:order 2.0 ;
    sh:path grace:biography_event .

g17sn:participates_in a rdfs:Property,
        sh:PropertyShape ;
    rdfs:label "participates in"@en ;
    volipi:message "Tooltip for participates in" ;
    dash:searchWidget sparnatural:ListProperty ;
    rdfs:subPropertyOf g17sn:event_object ;
    sh:class grace:event ;
    sh:name "participates in" ;
    sh:node g17sn:event ;
    sh:nodeKind sh:IRI ;
    sh:order 1.0 ;
    sh:path grace:participates_in .

This will not make the widget show a hierarchical property select.

Image

@tfrancart
Copy link
Contributor

No no no, you need to have this:

g17sn:biography_event a rdfs:Property,
        sh:PropertyShape ;
    rdfs:label "biography event"@en ;
    volipi:message "Tooltip for biography event" ;
    dash:searchWidget sparnatural:ListProperty ;
    sh:class grace:biographical_event ;
    sh:name "experienced" ;
    sh:node g17sn:biographical_event ;
    sh:nodeKind sh:IRI ;
    sh:order 2.0 ;
    sh:path grace:biography_event .

g17sn:participates_in a rdfs:Property,
        sh:PropertyShape ;
    rdfs:label "participates in"@en ;
    volipi:message "Tooltip for participates in" ;
    dash:searchWidget sparnatural:ListProperty ;
    sh:class grace:event ;
    sh:name "participates in" ;
    sh:node g17sn:event ;
    sh:nodeKind sh:IRI ;
    sh:order 1.0 ;
    sh:path grace:participates_in .

# HERE : 
grace:participates_in  rdfs:subPropertyOf grace:biography_event .

The subpropertyOf is between the properties themselves not between the shapes.

Besides, here too you shouldn't use rdfs:Property as a type of your shapes (remove triples g17sn:biography_event a rdfs:Property and g17sn:participates_in a rdfs:Property)

@ch-sander
Copy link
Author

Thanks! I wrote the SHACL analogue to the (working) SHACL definitions for classes:

g17sn:locality a rdfs:Class,
        sh:NodeShape ;
    rdfs:label "locality"@en ;
    volipi:iconName "fa-solid fa-map-marker-alt" ;
    volipi:message "A term for a specific area or neighborhood within a larger geographic or administrative region, often used in ecclesiastical contexts to specify church jurisdictions." ;
    rdfs:subClassOf g17sn:place ;
    sh:description "A term for a specific area or neighborhood within a larger geographic or administrative region, often used in ecclesiastical contexts to specify church jurisdictions." ;
    sh:name "locality" ;
    sh:nodeKind sh:IRI ;
    sh:order 2.0 ;
    sh:targetClass grace:locality .

g17sn:place a rdfs:Class,
        sh:NodeShape ;
    rdfs:label "place"@en ;
    volipi:iconName "fa-solid fa-map-marker-alt" ;
    volipi:message "Tooltip for place" ;
    rdfs:subClassOf g17sn:anything ;
    sh:name "place" ;
    sh:nodeKind sh:IRI ;
    sh:order 21.0 ;
    sh:property g17sn:contains,
        g17sn:falls_within,
        g17sn:has_main_type,
        g17sn:is_primary_place_of,
        g17sn:place_name,
        g17sn:wkt ;
    sh:targetClass grace:place .

that's why I said it behaves differently. Maybe the above is poorly done for SHACL, but it works with Sparnatural (widget shows hierarchy, no seperate OWL needed)

@ch-sander
Copy link
Author

But it works if I split OWL and SHACL

Image

I don't want to have them in two files, but could simply parse them in one: https://dataria.org/static/sparnatural/config.ttl

@ch-sander
Copy link
Author

@tfrancart sorry to reopen, but I fail to set more than one nested subProperty. Here a snippet:

g17sn:related_to_place_78 a sh:PropertyShape ;
    rdfs:label "related to place"@en ;
    volipi:message "what happened at this place" ;
    sh:class grace:place,
        g17raw:description ;
    sh:description "what happened at this place" ;
    sh:name "related to place" ;
    sh:nodeKind sh:IRI ;
    sh:or ( [ sh:class g17sn:place_54 ;
                sh:node g17sn:place_54 ] [ sh:class g17sn:description_171 ;
                sh:node g17sn:description_171 ] ) ;
    sh:order 1.0 ;
    sh:path grace:related_to_place .

grace:related_to_place a owl:ObjectProperty ;
    rdfs:label "related to place"@en ;
    rdfs:comment "what happened at this place" ;
    rdfs:domain [ a owl:Class ;
            owl:unionOf ( grace:event grace:object ) ] ;
    rdfs:range [ a owl:Class ;
            owl:unionOf ( grace:place g17raw:description ) ] ;
    rdfs:subPropertyOf grace:place_object .

g17sn:place_object_1180 a sh:PropertyShape ;
    rdfs:label "place object"@en ;
    volipi:message "Tooltip for place object" ;
    sh:class grace:place ;
    sh:name "place object" ;
    sh:node g17sn:place_54 ;
    sh:nodeKind sh:IRI ;
    sh:path grace:place_object .

grace:place_object a owl:ObjectProperty ;
    rdfs:label "place object"@en ;
    rdfs:domain grace:anything ;
    rdfs:range grace:place ;
    rdfs:subPropertyOf grace:refers_to .

g17sn:refers_to_173 a sh:PropertyShape ;
    rdfs:label "refers to"@en ;
    volipi:message "A wildcard property to find any direct entity relation" ;
    sh:class grace:anything ;
    sh:description "A wildcard property to find any direct entity relation" ;
    sh:name "refers to" ;
    sh:node g17sn:anything_914 ;
    sh:nodeKind sh:IRI ;
    sh:path grace:refers_to .

grace:refers_to a owl:ObjectProperty ;
    rdfs:label "refers to"@en ;
    rdfs:comment "A wildcard property to find any direct entity relation" ;
    rdfs:domain grace:anything ;
    rdfs:range grace:anything .

Issue

Why is g17sn:place_object_1180 correctly displayed as subPropertyOf of g17sn:refers_to_173 (Sparnatural), but g17sn:related_to_place_78 is not displayed as subPropertyOf of g17sn:place_object_1180?

Screenshots

Image

Image

Image

@ch-sander ch-sander reopened this Feb 15, 2025
@ch-sander
Copy link
Author

ch-sander commented Feb 15, 2025

I think it's due to a conflict in the properties' domains. I could resolve it but not quite sure what is the logical reason for it.

g17sn:anything_914 (sh:NodeShape) needs to list g17sn:related_to_place_78 in sh:property, it's not sufficient that g17sn:event_46 lists is, with sh:targetClass grace:event. As grace:event rdfs:subClassOf grace:anything, I assumed that's enough.

@tfrancart
Copy link
Contributor

@ch-sander I am bit lost. Could you share your complete not-working-as-expected config file ? so I can to reproduce
Thanks

@ch-sander
Copy link
Author

ch-sander commented Feb 19, 2025

@tfrancart sorry for the delay!

src=https://dataria.org/static/sparnatural/config.ttl https://dataria.org/file/results/rdf/ontology/graceful17.ttl

https://dataria.org/ask

Issue

General issue is that I don't quite understand how/why rdfs:subPropertyOf causes the UI to nest properties. It seems it also depends on the range and domain (in a non-trivial way) but sometimes not even this is the cause.

Exampe

See the (from my POV identical) specs snippet to define grace:falls_within as a property between two grace:place instances, and as a subPropertyOf grace:place_object. It does not work, although it works for grace:falls_within_calculated which seems to be defined pretty similar (if not identical).

Whether it works in this case depends on sh:path [ sh:oneOrMorePath grace:falls_within ]. If reduced to sh:path grace:falls_within, it works.

This is one example of many (all a bit different, not always due to a property path, but also relying on conforming range and domain and the respective sh:node and sh:property values). It might be that Sparnatural behaves exactly as it should (and in line with SHACL) but I want to make sure it does (at least get your confirmation) before I try to resolve all my cases that come as a surprise (to me). Again, it might all be my ignorance ;)....

g17sn:falls_within_954 a sh:PropertyShape ;
    rdfs:label "falls within"@en ;
    volipi:message "Tooltip for falls within (transitive property)" ;
    dash:searchWidget sparnatural:ListProperty ;
    sh:class grace:place ;
    sh:name "falls within" ;
    sh:node g17sn:place_54 ;
    sh:nodeKind sh:IRI ;
    sh:order 1.0 ;
    sh:path [ sh:oneOrMorePath grace:falls_within ] .

g17sn:falls_within_calculated_1347 a sh:PropertyShape ;
    rdfs:label "falls within calculated"@en ;
    volipi:message "Tooltip for falls within calculated" ;
    sh:class grace:place ;
    sh:name "falls within calculated" ;
    sh:node g17sn:place_54 ;
    sh:nodeKind sh:IRI ;
    sh:order 1.0 ;
    sh:path grace:falls_within_calculated .

grace:falls_within a owl:ObjectProperty ;
    rdfs:label "falls within"@en ;
    rdfs:domain grace:place ;
    rdfs:range grace:place ;
    rdfs:subPropertyOf grace:place_object .
    owl:inverseOf grace:contains .

grace:falls_within_calculated a owl:ObjectProperty ;
    rdfs:label "falls within calculated"@en ;
    rdfs:domain grace:place ;
    rdfs:range grace:place ;
    rdfs:subPropertyOf grace:place_object .

grace:place a owl:Class ;
    rdfs:label "place"@en ;
    rdfs:subClassOf [ a owl:Restriction ;
            owl:cardinality "1"^^xsd:nonNegativeInteger ;
            owl:onProperty grace:has_main_type ],
        [ a owl:Restriction ;
            owl:cardinality "1"^^xsd:nonNegativeInteger ;
            owl:onProperty grace:type_of_place ],
        grace:anything .

g17sn:place_54 a sh:NodeShape ;
    rdfs:label "place"@en ;
    volipi:iconName "fa-solid fa-map-marker-alt" ;
    volipi:message "Tooltip for place" ;
    sh:name "place" ;
    sh:nodeKind sh:IRI ;
    sh:order 21.0 ;
    sh:property g17sn:address_956,
        g17sn:contains_955,
        g17sn:event_object_917,
        g17sn:falls_within_954,
        g17sn:falls_within_calculated_1347,
        g17sn:falls_within_custom_1346,
        g17sn:geometry_1164,
        g17sn:has_main_type_1009,
        g17sn:institution_object_1183,
        g17sn:is_primary_place_of_1057,
        g17sn:person_object_1185,
        g17sn:place_literal_951,
        g17sn:place_name_953,
        g17sn:place_object_1180,
        g17sn:related_to_place_78,
        g17sn:type_object_1179,
        g17sn:type_of_place_58,
        g17sn:wkt_952 ;
    sh:targetClass grace:place .

g17sn:place_object_1180 a sh:PropertyShape ;
    rdfs:label "place object"@en ;
    volipi:message "Tooltip for place object" ;
    sh:class grace:place ;
    sh:name "place object" ;
    sh:node g17sn:place_54 ;
    sh:nodeKind sh:IRI ;
    sh:path grace:place_object .

Screenshots

Image
the property falls within is not nested in place object but base level
Image
the property falls within calculated is nested in place object

Expectation

I would have thought it works exactly like for rdfs:subClassOf, regardless of additional settings (such as range, domain, path, node, property, etc.)

@ch-sander
Copy link
Author

ch-sander commented Feb 19, 2025

Another example, that I couldn't figure out:

grace:has_main_type a owl:ObjectProperty ;
    rdfs:label "has main type"@en ;
    rdfs:domain [ a owl:Class ;
            owl:unionOf ( grace:entry grace:person grace:archive grace:source grace:event grace:type grace:place grace:institution grace:date grace:object grace:monetary_value grace:dedication grace:identifier grace:anything ) ] ;
    rdfs:range grace:type ;
    rdfs:subPropertyOf grace:type_object .

g17sn:has_main_type_1009 a sh:PropertyShape ;
    rdfs:label "has main type"@en ;
    volipi:message "Tooltip for has main type" ;
    dash:searchWidget sparnatural:ListProperty ;
    sh:class grace:type ;
    sh:maxCount 1 ;
    sh:minCount 1 ;
    sh:name "has main type" ;
    sh:node g17sn:type_47 ;
    sh:nodeKind sh:IRI ;
    sh:path grace:has_main_type .

why is this property grace:has_main_type not nested in grace:type_object but always at base level? I save long code blocks and screenshots upon request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants