github.com/gopherd/gonum@v0.0.4/graph/formats/cytoscapejs/cytoscapejs.go (about)

     1  // Copyright ©2018 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package cytoscapejs implements marshaling and unmarshaling of Cytoscape.js JSON documents.
     6  //
     7  // See http://js.cytoscape.org/ for Cytoscape.js documentation.
     8  package cytoscapejs // import "github.com/gopherd/gonum/graph/formats/cytoscapejs"
     9  
    10  import (
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  )
    15  
    16  // GraphElem is a Cytoscape.js graph with mixed graph elements.
    17  type GraphElem struct {
    18  	Elements []Element     `json:"elements"`
    19  	Layout   interface{}   `json:"layout,omitempty"`
    20  	Style    []interface{} `json:"style,omitempty"`
    21  }
    22  
    23  // Element is a mixed graph element.
    24  type Element struct {
    25  	Group            string      `json:"group,omitempty"`
    26  	Data             ElemData    `json:"data"`
    27  	Position         *Position   `json:"position,omitempty"`
    28  	RenderedPosition *Position   `json:"renderedPosition,omitempty"`
    29  	Selected         bool        `json:"selected,omitempty"`
    30  	Selectable       bool        `json:"selectable,omitempty"`
    31  	Locked           bool        `json:"locked,omitempty"`
    32  	Grabbable        bool        `json:"grabbable,omitempty"`
    33  	Classes          string      `json:"classes,omitempty"`
    34  	Scratch          interface{} `json:"scratch,omitempty"`
    35  }
    36  
    37  // ElemType describes an Element type.
    38  type ElemType int
    39  
    40  const (
    41  	InvalidElement ElemType = iota - 1
    42  	NodeElement
    43  	EdgeElement
    44  )
    45  
    46  // Type returns the element type of the receiver. It returns an error if the Element Group
    47  // is invalid or does not match the Element Data, or if the Elelement Data is an incomplete
    48  // edge.
    49  func (e Element) Type() (ElemType, error) {
    50  	et := InvalidElement
    51  	switch {
    52  	case e.Data.Source == "" && e.Data.Target == "":
    53  		et = NodeElement
    54  	case e.Data.Source != "" && e.Data.Target != "":
    55  		et = EdgeElement
    56  	default:
    57  		return et, errors.New("cytoscapejs: invalid element: incomplete edge")
    58  	}
    59  	switch {
    60  	case e.Group == "":
    61  		return et, nil
    62  	case e.Group == "node" && et == NodeElement:
    63  		return NodeElement, nil
    64  	case e.Group == "edge" && et == EdgeElement:
    65  		return NodeElement, nil
    66  	default:
    67  		return InvalidElement, errors.New("cytoscapejs: invalid element: mismatched group")
    68  	}
    69  }
    70  
    71  // ElemData is a graph element's data container.
    72  type ElemData struct {
    73  	ID         string
    74  	Source     string
    75  	Target     string
    76  	Parent     string
    77  	Attributes map[string]interface{}
    78  }
    79  
    80  var (
    81  	_ json.Marshaler   = (*ElemData)(nil)
    82  	_ json.Unmarshaler = (*ElemData)(nil)
    83  )
    84  
    85  // MarshalJSON implements the json.Marshaler interface.
    86  func (e *ElemData) MarshalJSON() ([]byte, error) {
    87  	if e.Attributes == nil {
    88  		type elem struct {
    89  			ID     string `json:"id"`
    90  			Source string `json:"source,omitempty"`
    91  			Target string `json:"target,omitempty"`
    92  			Parent string `json:"parent,omitempty"`
    93  		}
    94  		return json.Marshal(elem{ID: e.ID, Source: e.Source, Target: e.Target, Parent: e.Parent})
    95  	}
    96  	e.Attributes["id"] = e.ID
    97  	if e.Source != "" {
    98  		e.Attributes["source"] = e.Source
    99  	}
   100  	if e.Target != "" {
   101  		e.Attributes["target"] = e.Target
   102  	}
   103  	if e.Parent != "" {
   104  		e.Attributes["parent"] = e.Parent
   105  	}
   106  	b, err := json.Marshal(e.Attributes)
   107  	delete(e.Attributes, "id")
   108  	if e.Source != "" {
   109  		delete(e.Attributes, "source")
   110  	}
   111  	if e.Target != "" {
   112  		delete(e.Attributes, "target")
   113  	}
   114  	if e.Parent != "" {
   115  		delete(e.Attributes, "parent")
   116  	}
   117  	return b, err
   118  }
   119  
   120  // UnmarshalJSON implements the json.Unmarshaler interface.
   121  func (e *ElemData) UnmarshalJSON(data []byte) error {
   122  	var attrs map[string]interface{}
   123  	err := json.Unmarshal(data, &attrs)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	id, ok := attrs["id"]
   128  	if !ok {
   129  		return errors.New("cytoscapejs: no ID")
   130  	}
   131  	e.ID = fmt.Sprint(id)
   132  	source, ok := attrs["source"]
   133  	if ok {
   134  		e.Source = fmt.Sprint(source)
   135  	}
   136  	target, ok := attrs["target"]
   137  	if ok {
   138  		e.Target = fmt.Sprint(target)
   139  	}
   140  	p, ok := attrs["parent"]
   141  	if ok {
   142  		e.Parent = fmt.Sprint(p)
   143  	}
   144  	delete(attrs, "id")
   145  	delete(attrs, "source")
   146  	delete(attrs, "target")
   147  	delete(attrs, "parent")
   148  	if len(attrs) != 0 {
   149  		e.Attributes = attrs
   150  	}
   151  	return nil
   152  }
   153  
   154  // GraphNodeEdge is a Cytoscape.js graph with separated nodes and edges.
   155  type GraphNodeEdge struct {
   156  	Elements Elements      `json:"elements"`
   157  	Layout   interface{}   `json:"layout,omitempty"`
   158  	Style    []interface{} `json:"style,omitempty"`
   159  }
   160  
   161  // Elements contains the nodes and edges of a GraphNodeEdge.
   162  type Elements struct {
   163  	Nodes []Node `json:"nodes"`
   164  	Edges []Edge `json:"edges"`
   165  }
   166  
   167  // Node is a Cytoscape.js node.
   168  type Node struct {
   169  	Data             NodeData    `json:"data"`
   170  	Position         *Position   `json:"position,omitempty"`
   171  	RenderedPosition *Position   `json:"renderedPosition,omitempty"`
   172  	Selected         bool        `json:"selected,omitempty"`
   173  	Selectable       bool        `json:"selectable,omitempty"`
   174  	Locked           bool        `json:"locked,omitempty"`
   175  	Grabbable        bool        `json:"grabbable,omitempty"`
   176  	Classes          string      `json:"classes,omitempty"`
   177  	Scratch          interface{} `json:"scratch,omitempty"`
   178  }
   179  
   180  // NodeData is a graph node's data container.
   181  type NodeData struct {
   182  	ID         string
   183  	Parent     string
   184  	Attributes map[string]interface{}
   185  }
   186  
   187  var (
   188  	_ json.Marshaler   = (*NodeData)(nil)
   189  	_ json.Unmarshaler = (*NodeData)(nil)
   190  )
   191  
   192  // MarshalJSON implements the json.Marshaler interface.
   193  func (n *NodeData) MarshalJSON() ([]byte, error) {
   194  	if n.Attributes == nil {
   195  		type node struct {
   196  			ID     string `json:"id"`
   197  			Parent string `json:"parent,omitempty"`
   198  		}
   199  		return json.Marshal(node{ID: n.ID, Parent: n.Parent})
   200  	}
   201  	n.Attributes["id"] = n.ID
   202  	n.Attributes["parent"] = n.Parent
   203  	b, err := json.Marshal(n.Attributes)
   204  	delete(n.Attributes, "id")
   205  	delete(n.Attributes, "parent")
   206  	return b, err
   207  }
   208  
   209  // UnmarshalJSON implements the json.Unmarshaler interface.
   210  func (n *NodeData) UnmarshalJSON(data []byte) error {
   211  	var attrs map[string]interface{}
   212  	err := json.Unmarshal(data, &attrs)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	id, ok := attrs["id"]
   217  	if !ok {
   218  		return errors.New("cytoscapejs: no ID")
   219  	}
   220  	n.ID = fmt.Sprint(id)
   221  	delete(attrs, "id")
   222  	p, ok := attrs["parent"]
   223  	if ok {
   224  		n.Parent = fmt.Sprint(p)
   225  	}
   226  	delete(attrs, "parent")
   227  	if len(attrs) != 0 {
   228  		n.Attributes = attrs
   229  	}
   230  	return nil
   231  }
   232  
   233  // Edge is a Cytoscape.js edge.
   234  type Edge struct {
   235  	Data       EdgeData    `json:"data"`
   236  	Selected   bool        `json:"selected,omitempty"`
   237  	Selectable bool        `json:"selectable,omitempty"`
   238  	Classes    string      `json:"classes,omitempty"`
   239  	Scratch    interface{} `json:"scratch,omitempty"`
   240  }
   241  
   242  // EdgeData is a graph edge's data container.
   243  type EdgeData struct {
   244  	ID         string
   245  	Source     string
   246  	Target     string
   247  	Attributes map[string]interface{}
   248  }
   249  
   250  var (
   251  	_ json.Marshaler   = (*EdgeData)(nil)
   252  	_ json.Unmarshaler = (*EdgeData)(nil)
   253  )
   254  
   255  // MarshalJSON implements the json.Marshaler interface.
   256  func (e *EdgeData) MarshalJSON() ([]byte, error) {
   257  	if e.Attributes == nil {
   258  		type edge struct {
   259  			ID     string `json:"id"`
   260  			Source string `json:"source"`
   261  			Target string `json:"target"`
   262  		}
   263  		return json.Marshal(edge{ID: e.ID, Source: e.Source, Target: e.Target})
   264  	}
   265  	e.Attributes["id"] = e.ID
   266  	e.Attributes["source"] = e.Source
   267  	e.Attributes["target"] = e.Target
   268  	b, err := json.Marshal(e.Attributes)
   269  	delete(e.Attributes, "id")
   270  	delete(e.Attributes, "source")
   271  	delete(e.Attributes, "target")
   272  	return b, err
   273  }
   274  
   275  // UnmarshalJSON implements the json.Unmarshaler interface.
   276  func (e *EdgeData) UnmarshalJSON(data []byte) error {
   277  	var attrs map[string]interface{}
   278  	err := json.Unmarshal(data, &attrs)
   279  	if err != nil {
   280  		return err
   281  	}
   282  	id, ok := attrs["id"]
   283  	if !ok {
   284  		return errors.New("cytoscapejs: no ID")
   285  	}
   286  	source, ok := attrs["source"]
   287  	if !ok {
   288  		return errors.New("cytoscapejs: no source")
   289  	}
   290  	target, ok := attrs["target"]
   291  	if !ok {
   292  		return errors.New("cytoscapejs: no target")
   293  	}
   294  	e.ID = fmt.Sprint(id)
   295  	e.Source = fmt.Sprint(source)
   296  	e.Target = fmt.Sprint(target)
   297  	delete(attrs, "id")
   298  	delete(attrs, "source")
   299  	delete(attrs, "target")
   300  	if len(attrs) != 0 {
   301  		e.Attributes = attrs
   302  	}
   303  	return nil
   304  }
   305  
   306  // Position is a node position.
   307  type Position struct {
   308  	X float64 `json:"x"`
   309  	Y float64 `json:"y"`
   310  }