mirror of
https://github.com/gonum/gonum.git
synced 2025-10-06 23:52:47 +08:00
311 lines
8.0 KiB
Go
311 lines
8.0 KiB
Go
// Copyright ©2018 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package cytoscapejs implements marshaling and unmarshaling of Cytoscape.js JSON documents.
|
|
//
|
|
// See http://js.cytoscape.org/ for Cytoscape.js documentation.
|
|
package cytoscapejs // import "gonum.org/v1/gonum/graph/formats/cytoscapejs"
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// GraphElem is a Cytoscape.js graph with mixed graph elements.
|
|
type GraphElem struct {
|
|
Elements []Element `json:"elements"`
|
|
Layout interface{} `json:"layout,omitempty"`
|
|
Style []interface{} `json:"style,omitempty"`
|
|
}
|
|
|
|
// Element is a mixed graph element.
|
|
type Element struct {
|
|
Group string `json:"group,omitempty"`
|
|
Data ElemData `json:"data"`
|
|
Position *Position `json:"position,omitempty"`
|
|
RenderedPosition *Position `json:"renderedPosition,omitempty"`
|
|
Selected bool `json:"selected,omitempty"`
|
|
Selectable bool `json:"selectable,omitempty"`
|
|
Locked bool `json:"locked,omitempty"`
|
|
Grabbable bool `json:"grabbable,omitempty"`
|
|
Classes string `json:"classes,omitempty"`
|
|
Scratch interface{} `json:"scratch,omitempty"`
|
|
}
|
|
|
|
// ElemType describes an Element type.
|
|
type ElemType int
|
|
|
|
const (
|
|
InvalidElement ElemType = iota - 1
|
|
NodeElement
|
|
EdgeElement
|
|
)
|
|
|
|
// Type returns the element type of the receiver. It returns an error if the Element Group
|
|
// is invalid or does not match the Element Data, or if the Element Data is an incomplete
|
|
// edge.
|
|
func (e Element) Type() (ElemType, error) {
|
|
et := InvalidElement
|
|
switch {
|
|
case e.Data.Source == "" && e.Data.Target == "":
|
|
et = NodeElement
|
|
case e.Data.Source != "" && e.Data.Target != "":
|
|
et = EdgeElement
|
|
default:
|
|
return et, errors.New("cytoscapejs: invalid element: incomplete edge")
|
|
}
|
|
switch {
|
|
case e.Group == "":
|
|
return et, nil
|
|
case e.Group == "node" && et == NodeElement:
|
|
return NodeElement, nil
|
|
case e.Group == "edge" && et == EdgeElement:
|
|
return NodeElement, nil
|
|
default:
|
|
return InvalidElement, errors.New("cytoscapejs: invalid element: mismatched group")
|
|
}
|
|
}
|
|
|
|
// ElemData is a graph element's data container.
|
|
type ElemData struct {
|
|
ID string
|
|
Source string
|
|
Target string
|
|
Parent string
|
|
Attributes map[string]interface{}
|
|
}
|
|
|
|
var (
|
|
_ json.Marshaler = (*ElemData)(nil)
|
|
_ json.Unmarshaler = (*ElemData)(nil)
|
|
)
|
|
|
|
// MarshalJSON implements the json.Marshaler interface.
|
|
func (e *ElemData) MarshalJSON() ([]byte, error) {
|
|
if e.Attributes == nil {
|
|
type elem struct {
|
|
ID string `json:"id"`
|
|
Source string `json:"source,omitempty"`
|
|
Target string `json:"target,omitempty"`
|
|
Parent string `json:"parent,omitempty"`
|
|
}
|
|
return json.Marshal(elem{ID: e.ID, Source: e.Source, Target: e.Target, Parent: e.Parent})
|
|
}
|
|
e.Attributes["id"] = e.ID
|
|
if e.Source != "" {
|
|
e.Attributes["source"] = e.Source
|
|
}
|
|
if e.Target != "" {
|
|
e.Attributes["target"] = e.Target
|
|
}
|
|
if e.Parent != "" {
|
|
e.Attributes["parent"] = e.Parent
|
|
}
|
|
b, err := json.Marshal(e.Attributes)
|
|
delete(e.Attributes, "id")
|
|
if e.Source != "" {
|
|
delete(e.Attributes, "source")
|
|
}
|
|
if e.Target != "" {
|
|
delete(e.Attributes, "target")
|
|
}
|
|
if e.Parent != "" {
|
|
delete(e.Attributes, "parent")
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
|
func (e *ElemData) UnmarshalJSON(data []byte) error {
|
|
var attrs map[string]interface{}
|
|
err := json.Unmarshal(data, &attrs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, ok := attrs["id"]
|
|
if !ok {
|
|
return errors.New("cytoscapejs: no ID")
|
|
}
|
|
e.ID = fmt.Sprint(id)
|
|
source, ok := attrs["source"]
|
|
if ok {
|
|
e.Source = fmt.Sprint(source)
|
|
}
|
|
target, ok := attrs["target"]
|
|
if ok {
|
|
e.Target = fmt.Sprint(target)
|
|
}
|
|
p, ok := attrs["parent"]
|
|
if ok {
|
|
e.Parent = fmt.Sprint(p)
|
|
}
|
|
delete(attrs, "id")
|
|
delete(attrs, "source")
|
|
delete(attrs, "target")
|
|
delete(attrs, "parent")
|
|
if len(attrs) != 0 {
|
|
e.Attributes = attrs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GraphNodeEdge is a Cytoscape.js graph with separated nodes and edges.
|
|
type GraphNodeEdge struct {
|
|
Elements Elements `json:"elements"`
|
|
Layout interface{} `json:"layout,omitempty"`
|
|
Style []interface{} `json:"style,omitempty"`
|
|
}
|
|
|
|
// Elements contains the nodes and edges of a GraphNodeEdge.
|
|
type Elements struct {
|
|
Nodes []Node `json:"nodes"`
|
|
Edges []Edge `json:"edges"`
|
|
}
|
|
|
|
// Node is a Cytoscape.js node.
|
|
type Node struct {
|
|
Data NodeData `json:"data"`
|
|
Position *Position `json:"position,omitempty"`
|
|
RenderedPosition *Position `json:"renderedPosition,omitempty"`
|
|
Selected bool `json:"selected,omitempty"`
|
|
Selectable bool `json:"selectable,omitempty"`
|
|
Locked bool `json:"locked,omitempty"`
|
|
Grabbable bool `json:"grabbable,omitempty"`
|
|
Classes string `json:"classes,omitempty"`
|
|
Scratch interface{} `json:"scratch,omitempty"`
|
|
}
|
|
|
|
// NodeData is a graph node's data container.
|
|
type NodeData struct {
|
|
ID string
|
|
Parent string
|
|
Attributes map[string]interface{}
|
|
}
|
|
|
|
var (
|
|
_ json.Marshaler = (*NodeData)(nil)
|
|
_ json.Unmarshaler = (*NodeData)(nil)
|
|
)
|
|
|
|
// MarshalJSON implements the json.Marshaler interface.
|
|
func (n *NodeData) MarshalJSON() ([]byte, error) {
|
|
if n.Attributes == nil {
|
|
type node struct {
|
|
ID string `json:"id"`
|
|
Parent string `json:"parent,omitempty"`
|
|
}
|
|
return json.Marshal(node{ID: n.ID, Parent: n.Parent})
|
|
}
|
|
n.Attributes["id"] = n.ID
|
|
n.Attributes["parent"] = n.Parent
|
|
b, err := json.Marshal(n.Attributes)
|
|
delete(n.Attributes, "id")
|
|
delete(n.Attributes, "parent")
|
|
return b, err
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
|
func (n *NodeData) UnmarshalJSON(data []byte) error {
|
|
var attrs map[string]interface{}
|
|
err := json.Unmarshal(data, &attrs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, ok := attrs["id"]
|
|
if !ok {
|
|
return errors.New("cytoscapejs: no ID")
|
|
}
|
|
n.ID = fmt.Sprint(id)
|
|
delete(attrs, "id")
|
|
p, ok := attrs["parent"]
|
|
if ok {
|
|
n.Parent = fmt.Sprint(p)
|
|
}
|
|
delete(attrs, "parent")
|
|
if len(attrs) != 0 {
|
|
n.Attributes = attrs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Edge is a Cytoscape.js edge.
|
|
type Edge struct {
|
|
Data EdgeData `json:"data"`
|
|
Selected bool `json:"selected,omitempty"`
|
|
Selectable bool `json:"selectable,omitempty"`
|
|
Classes string `json:"classes,omitempty"`
|
|
Scratch interface{} `json:"scratch,omitempty"`
|
|
}
|
|
|
|
// EdgeData is a graph edge's data container.
|
|
type EdgeData struct {
|
|
ID string
|
|
Source string
|
|
Target string
|
|
Attributes map[string]interface{}
|
|
}
|
|
|
|
var (
|
|
_ json.Marshaler = (*EdgeData)(nil)
|
|
_ json.Unmarshaler = (*EdgeData)(nil)
|
|
)
|
|
|
|
// MarshalJSON implements the json.Marshaler interface.
|
|
func (e *EdgeData) MarshalJSON() ([]byte, error) {
|
|
if e.Attributes == nil {
|
|
type edge struct {
|
|
ID string `json:"id"`
|
|
Source string `json:"source"`
|
|
Target string `json:"target"`
|
|
}
|
|
return json.Marshal(edge{ID: e.ID, Source: e.Source, Target: e.Target})
|
|
}
|
|
e.Attributes["id"] = e.ID
|
|
e.Attributes["source"] = e.Source
|
|
e.Attributes["target"] = e.Target
|
|
b, err := json.Marshal(e.Attributes)
|
|
delete(e.Attributes, "id")
|
|
delete(e.Attributes, "source")
|
|
delete(e.Attributes, "target")
|
|
return b, err
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
|
func (e *EdgeData) UnmarshalJSON(data []byte) error {
|
|
var attrs map[string]interface{}
|
|
err := json.Unmarshal(data, &attrs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, ok := attrs["id"]
|
|
if !ok {
|
|
return errors.New("cytoscapejs: no ID")
|
|
}
|
|
source, ok := attrs["source"]
|
|
if !ok {
|
|
return errors.New("cytoscapejs: no source")
|
|
}
|
|
target, ok := attrs["target"]
|
|
if !ok {
|
|
return errors.New("cytoscapejs: no target")
|
|
}
|
|
e.ID = fmt.Sprint(id)
|
|
e.Source = fmt.Sprint(source)
|
|
e.Target = fmt.Sprint(target)
|
|
delete(attrs, "id")
|
|
delete(attrs, "source")
|
|
delete(attrs, "target")
|
|
if len(attrs) != 0 {
|
|
e.Attributes = attrs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Position is a node position.
|
|
type Position struct {
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
}
|