Add filter, add READMES
This commit is contained in:
208
backend_go/graph_snapshot.go
Normal file
208
backend_go/graph_snapshot.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
graphqueries "visualizador_instanciados/backend_go/graph_queries"
|
||||
)
|
||||
|
||||
const rdfsLabelIRI = "http://www.w3.org/2000/01/rdf-schema#label"
|
||||
|
||||
func fetchGraphSnapshot(
|
||||
ctx context.Context,
|
||||
sparql *AnzoGraphClient,
|
||||
cfg Config,
|
||||
nodeLimit int,
|
||||
edgeLimit int,
|
||||
graphQueryID string,
|
||||
) (GraphResponse, error) {
|
||||
def, ok := graphqueries.Get(graphQueryID)
|
||||
if !ok {
|
||||
return GraphResponse{}, fmt.Errorf("unknown graph_query_id: %s", graphQueryID)
|
||||
}
|
||||
edgesQ := def.EdgeQuery(edgeLimit, cfg.IncludeBNodes)
|
||||
raw, err := sparql.Query(ctx, edgesQ)
|
||||
if err != nil {
|
||||
return GraphResponse{}, err
|
||||
}
|
||||
|
||||
var res sparqlResponse
|
||||
if err := json.Unmarshal(raw, &res); err != nil {
|
||||
return GraphResponse{}, fmt.Errorf("failed to parse SPARQL JSON: %w", err)
|
||||
}
|
||||
|
||||
nodes, edges := graphFromSparqlBindings(res.Results.Bindings, nodeLimit, cfg.IncludeBNodes)
|
||||
|
||||
// Layout: invert edges for hierarchy (target -> source).
|
||||
hierEdges := make([][2]int, 0, len(edges))
|
||||
for _, e := range edges {
|
||||
hierEdges = append(hierEdges, [2]int{e.Target, e.Source})
|
||||
}
|
||||
|
||||
layers, cycleErr := levelSynchronousKahnLayers(len(nodes), hierEdges)
|
||||
if cycleErr != nil {
|
||||
sample := make([]string, 0, 20)
|
||||
for _, nid := range cycleErr.RemainingNodeIDs {
|
||||
if len(sample) >= 20 {
|
||||
break
|
||||
}
|
||||
if nid >= 0 && nid < len(nodes) {
|
||||
sample = append(sample, nodes[nid].IRI)
|
||||
}
|
||||
}
|
||||
cycleErr.RemainingIRISample = sample
|
||||
return GraphResponse{}, cycleErr
|
||||
}
|
||||
|
||||
idToIRI := make([]string, len(nodes))
|
||||
for i := range nodes {
|
||||
idToIRI[i] = nodes[i].IRI
|
||||
}
|
||||
for _, layer := range layers {
|
||||
sortLayerByIRI(layer, idToIRI)
|
||||
}
|
||||
|
||||
xs, ys := radialPositionsFromLayers(len(nodes), layers, 5000.0)
|
||||
for i := range nodes {
|
||||
nodes[i].X = xs[i]
|
||||
nodes[i].Y = ys[i]
|
||||
}
|
||||
|
||||
// Attach labels for URI nodes.
|
||||
iris := make([]string, 0)
|
||||
for _, n := range nodes {
|
||||
if n.TermType == "uri" && n.IRI != "" {
|
||||
iris = append(iris, n.IRI)
|
||||
}
|
||||
}
|
||||
if len(iris) > 0 {
|
||||
labelByIRI, err := fetchRDFSLabels(ctx, sparql, iris, 500)
|
||||
if err != nil {
|
||||
return GraphResponse{}, err
|
||||
}
|
||||
for i := range nodes {
|
||||
if nodes[i].TermType != "uri" {
|
||||
continue
|
||||
}
|
||||
lbl, ok := labelByIRI[nodes[i].IRI]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
val := lbl
|
||||
nodes[i].Label = &val
|
||||
}
|
||||
}
|
||||
|
||||
meta := &GraphMeta{
|
||||
Backend: "anzograph",
|
||||
TTLPath: nil,
|
||||
SparqlEndpoint: cfg.EffectiveSparqlEndpoint(),
|
||||
IncludeBNodes: cfg.IncludeBNodes,
|
||||
GraphQueryID: graphQueryID,
|
||||
NodeLimit: nodeLimit,
|
||||
EdgeLimit: edgeLimit,
|
||||
Nodes: len(nodes),
|
||||
Edges: len(edges),
|
||||
}
|
||||
|
||||
return GraphResponse{Nodes: nodes, Edges: edges, Meta: meta}, nil
|
||||
}
|
||||
|
||||
type bestLabel struct {
|
||||
score int
|
||||
value string
|
||||
}
|
||||
|
||||
func fetchRDFSLabels(
|
||||
ctx context.Context,
|
||||
sparql *AnzoGraphClient,
|
||||
iris []string,
|
||||
batchSize int,
|
||||
) (map[string]string, error) {
|
||||
best := make(map[string]bestLabel)
|
||||
|
||||
for i := 0; i < len(iris); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(iris) {
|
||||
end = len(iris)
|
||||
}
|
||||
batch := iris[i:end]
|
||||
|
||||
values := make([]string, 0, len(batch))
|
||||
for _, u := range batch {
|
||||
values = append(values, "<"+u+">")
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`
|
||||
SELECT ?s ?label
|
||||
WHERE {
|
||||
VALUES ?s { %s }
|
||||
?s <%s> ?label .
|
||||
}
|
||||
`, strings.Join(values, " "), rdfsLabelIRI)
|
||||
|
||||
raw, err := sparql.Query(ctx, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res sparqlResponse
|
||||
if err := json.Unmarshal(raw, &res); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse SPARQL JSON: %w", err)
|
||||
}
|
||||
|
||||
for _, b := range res.Results.Bindings {
|
||||
sTerm, ok := b["s"]
|
||||
if !ok || sTerm.Value == "" {
|
||||
continue
|
||||
}
|
||||
lblTerm, ok := b["label"]
|
||||
if !ok || lblTerm.Type != "literal" || lblTerm.Value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
score := labelScore(lblTerm.Lang)
|
||||
prev, ok := best[sTerm.Value]
|
||||
if !ok || score > prev.score {
|
||||
best[sTerm.Value] = bestLabel{score: score, value: lblTerm.Value}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out := make(map[string]string, len(best))
|
||||
for iri, v := range best {
|
||||
out[iri] = v.value
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func labelScore(lang string) int {
|
||||
lang = strings.ToLower(strings.TrimSpace(lang))
|
||||
if lang == "en" {
|
||||
return 3
|
||||
}
|
||||
if lang == "" {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func sortIntsUnique(xs []int) []int {
|
||||
if len(xs) == 0 {
|
||||
return xs
|
||||
}
|
||||
sort.Ints(xs)
|
||||
out := xs[:0]
|
||||
var last int
|
||||
for i, v := range xs {
|
||||
if i == 0 || v != last {
|
||||
out = append(out, v)
|
||||
}
|
||||
last = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user