gonum.org/v1/gonum@v0.14.0/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 "gonum.org/v1/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 Element 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 }