mirror of
https://github.com/gonum/gonum.git
synced 2025-10-07 08:01:20 +08:00
graph/formats: add packages for serialising graphs to js rendering engines
This commit is contained in:
310
graph/formats/cytoscapejs/cytoscapejs.go
Normal file
310
graph/formats/cytoscapejs/cytoscapejs.go
Normal file
@@ -0,0 +1,310 @@
|
||||
// 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 Elelement 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 edge struct {
|
||||
ID string `json:"id"`
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
}
|
||||
return json.Marshal(edge{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"`
|
||||
}
|
371
graph/formats/cytoscapejs/cytoscapejs_test.go
Normal file
371
graph/formats/cytoscapejs/cytoscapejs_test.go
Normal file
@@ -0,0 +1,371 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cytoscapejsElementsTests = []struct {
|
||||
path string
|
||||
wantNodes int
|
||||
wantEdges int
|
||||
wantGraph []Element
|
||||
wantAttributes []string
|
||||
}{
|
||||
{
|
||||
path: "edge-type.json",
|
||||
wantNodes: 10,
|
||||
wantEdges: 10,
|
||||
wantGraph: []Element{
|
||||
{Data: ElemData{ID: "n01", Attributes: map[string]interface{}{"type": "bezier"}}},
|
||||
{Data: ElemData{ID: "n02"}},
|
||||
{Data: ElemData{ID: "e01", Source: "n01", Target: "n02"}, Classes: "bezier"},
|
||||
{Data: ElemData{ID: "e02", Source: "n01", Target: "n02"}, Classes: "bezier"},
|
||||
{Data: ElemData{ID: "e03", Source: "n02", Target: "n01"}, Classes: "bezier"},
|
||||
{Data: ElemData{ID: "n03", Attributes: map[string]interface{}{"type": "unbundled-bezier"}}},
|
||||
{Data: ElemData{ID: "n04"}},
|
||||
{Data: ElemData{ID: "e04", Source: "n03", Target: "n04"}, Classes: "unbundled-bezier"},
|
||||
{Data: ElemData{ID: "n05", Attributes: map[string]interface{}{"type": "unbundled-bezier(multiple)"}}},
|
||||
{Data: ElemData{ID: "n06"}},
|
||||
{Data: ElemData{ID: "e05", Source: "n05", Target: "n06", Parent: ""}, Classes: "multi-unbundled-bezier"},
|
||||
{Data: ElemData{ID: "n07", Attributes: map[string]interface{}{"type": "haystack"}}},
|
||||
{Data: ElemData{ID: "n08"}},
|
||||
{Data: ElemData{ID: "e06", Source: "n08", Target: "n07"}, Classes: "haystack"},
|
||||
{Data: ElemData{ID: "e07", Source: "n08", Target: "n07"}, Classes: "haystack"},
|
||||
{Data: ElemData{ID: "e08", Source: "n08", Target: "n07"}, Classes: "haystack"},
|
||||
{Data: ElemData{ID: "e09", Source: "n08", Target: "n07"}, Classes: "haystack"},
|
||||
{Data: ElemData{ID: "n09", Attributes: map[string]interface{}{"type": "segments"}}},
|
||||
{Data: ElemData{ID: "n10"}},
|
||||
{Data: ElemData{ID: "e10", Source: "n09", Target: "n10"}, Classes: "segments"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshalElements(t *testing.T) {
|
||||
for _, test := range cytoscapejsElementsTests {
|
||||
data, err := ioutil.ReadFile(filepath.Join("testdata", test.path))
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var got []Element
|
||||
err = json.Unmarshal(data, &got)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var gotNodes, gotEdges int
|
||||
for _, e := range got {
|
||||
typ, err := e.Type()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error finding element type for %+v: %v", e, err)
|
||||
}
|
||||
switch typ {
|
||||
case NodeElement:
|
||||
gotNodes++
|
||||
case EdgeElement:
|
||||
gotEdges++
|
||||
}
|
||||
}
|
||||
|
||||
if gotNodes != test.wantNodes {
|
||||
t.Errorf("unexpected result for order of %q: got:%d want:%d", test.path, gotNodes, test.wantNodes)
|
||||
}
|
||||
if gotEdges != test.wantEdges {
|
||||
t.Errorf("unexpected result for size of %q: got:%d want:%d", test.path, gotEdges, test.wantEdges)
|
||||
}
|
||||
if test.wantGraph != nil && !reflect.DeepEqual(got, test.wantGraph) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got, test.wantGraph)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalElements(t *testing.T) {
|
||||
for _, test := range cytoscapejsElementsTests {
|
||||
data, err := ioutil.ReadFile(filepath.Join("testdata", test.path))
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var want []Element
|
||||
err = json.Unmarshal(data, &want)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
marshaled, err := json.Marshal(want)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var got []Element
|
||||
err = json.Unmarshal(marshaled, &got)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cytoscapejsNodeEdgeTests = []struct {
|
||||
path string
|
||||
wantNodes int
|
||||
wantEdges int
|
||||
wantGraph *Elements
|
||||
firstNode Node
|
||||
firstEdge Edge
|
||||
wantNodeAttributes map[string]bool
|
||||
wantEdgeAttributes map[string]bool
|
||||
}{
|
||||
{
|
||||
path: "cola-compound.json",
|
||||
wantNodes: 9,
|
||||
wantEdges: 7,
|
||||
wantGraph: &Elements{
|
||||
Nodes: []Node{
|
||||
{Data: NodeData{ID: "compound-1", Parent: ""}},
|
||||
{Data: NodeData{ID: "compound-2", Parent: ""}},
|
||||
{Data: NodeData{ID: "compound-3", Parent: ""}},
|
||||
{Data: NodeData{ID: "b", Parent: "compound-1"}},
|
||||
{Data: NodeData{ID: "c", Parent: "compound-1"}},
|
||||
{Data: NodeData{ID: "a", Parent: "compound-2"}},
|
||||
{Data: NodeData{ID: "d", Parent: "compound-3"}},
|
||||
{Data: NodeData{ID: "e", Parent: "compound-3"}},
|
||||
{Data: NodeData{ID: "f", Parent: ""}},
|
||||
},
|
||||
Edges: []Edge{
|
||||
{Data: EdgeData{ID: "ab", Source: "a", Target: "b"}},
|
||||
{Data: EdgeData{ID: "bc", Source: "b", Target: "c"}},
|
||||
{Data: EdgeData{ID: "ac", Source: "a", Target: "c"}},
|
||||
{Data: EdgeData{ID: "cd", Source: "c", Target: "d"}},
|
||||
{Data: EdgeData{ID: "de", Source: "d", Target: "e"}},
|
||||
{Data: EdgeData{ID: "df", Source: "d", Target: "f"}},
|
||||
{Data: EdgeData{ID: "af", Source: "a", Target: "f"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "tokyo-railways.json",
|
||||
wantNodes: 943,
|
||||
wantEdges: 860,
|
||||
firstNode: Node{
|
||||
Data: NodeData{
|
||||
ID: "8220",
|
||||
Attributes: map[string]interface{}{
|
||||
"station_name": "京成高砂",
|
||||
"close_ymd": "",
|
||||
"lon": 139.866875,
|
||||
"post": "",
|
||||
"e_status": 0.0,
|
||||
"SUID": 8220.0,
|
||||
"station_g_cd": 2300110.0,
|
||||
"add": "東京都葛飾区高砂五丁目28-1",
|
||||
"line_cd": 99340.0,
|
||||
"selected": false,
|
||||
"open_ymd": "",
|
||||
"name": "9934001",
|
||||
"pref_name": "東京都",
|
||||
"shared_name": "9934001",
|
||||
"lat": 35.750932,
|
||||
"x": 1398668.75,
|
||||
"y": -357509.32,
|
||||
},
|
||||
},
|
||||
Position: Position{
|
||||
X: 1398668.75,
|
||||
Y: -357509.32,
|
||||
},
|
||||
},
|
||||
firstEdge: Edge{
|
||||
Data: EdgeData{
|
||||
ID: "18417",
|
||||
Source: "8220",
|
||||
Target: "8221",
|
||||
Attributes: map[string]interface{}{
|
||||
"line_name_k": "ホクソウテツドウホクソウセン",
|
||||
"is_bullet": false,
|
||||
"lon": 140.03784499075186,
|
||||
"company_name_k": "ホクソウテツドウ",
|
||||
"zoom": 11.0,
|
||||
"SUID": 18417.0,
|
||||
"company_type": 0.0,
|
||||
"company_name_h": "北総鉄道株式会社",
|
||||
"interaction": "99340",
|
||||
"shared_interaction": "99340",
|
||||
"company_url": "http://www.hokuso-railway.co.jp/",
|
||||
"line_name": "北総鉄道北総線",
|
||||
"selected": false,
|
||||
"company_name": "北総鉄道",
|
||||
"company_cd": 152.0,
|
||||
"name": "9934001 (99340) 9934002",
|
||||
"rr_cd": 99.0,
|
||||
"company_name_r": "北総鉄道",
|
||||
"e_status_x": 0.0,
|
||||
"shared_name": "9934001 (99340) 9934002",
|
||||
"lat": 35.78346285846615,
|
||||
"e_status_y": 0.0,
|
||||
"line_name_h": "北総鉄道北総線",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantNodeAttributes: map[string]bool{
|
||||
"station_name": true,
|
||||
"close_ymd": true,
|
||||
"lon": true,
|
||||
"post": true,
|
||||
"e_status": true,
|
||||
"SUID": true,
|
||||
"station_g_cd": true,
|
||||
"add": true,
|
||||
"line_cd": true,
|
||||
"selected": true,
|
||||
"open_ymd": true,
|
||||
"name": true,
|
||||
"pref_name": true,
|
||||
"shared_name": true,
|
||||
"lat": true,
|
||||
"x": true,
|
||||
"y": true,
|
||||
},
|
||||
wantEdgeAttributes: map[string]bool{
|
||||
"line_name_k": true,
|
||||
"is_bullet": true,
|
||||
"lon": true,
|
||||
"company_name_k": true,
|
||||
"zoom": true,
|
||||
"SUID": true,
|
||||
"company_type": true,
|
||||
"company_name_h": true,
|
||||
"interaction": true,
|
||||
"shared_interaction": true,
|
||||
"company_url": true,
|
||||
"line_name": true,
|
||||
"selected": true,
|
||||
"company_name": true,
|
||||
"company_cd": true,
|
||||
"name": true,
|
||||
"rr_cd": true,
|
||||
"company_name_r": true,
|
||||
"e_status_x": true,
|
||||
"shared_name": true,
|
||||
"lat": true,
|
||||
"e_status_y": true,
|
||||
"line_name_h": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshalNodeEdge(t *testing.T) {
|
||||
for _, test := range cytoscapejsNodeEdgeTests {
|
||||
data, err := ioutil.ReadFile(filepath.Join("testdata", test.path))
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var got Elements
|
||||
err = json.Unmarshal(data, &got)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(got.Nodes) != test.wantNodes {
|
||||
t.Errorf("unexpected result for order of %q: got:%d want:%d", test.path, len(got.Nodes), test.wantNodes)
|
||||
}
|
||||
if len(got.Edges) != test.wantEdges {
|
||||
t.Errorf("unexpected result for size of %q: got:%d want:%d", test.path, len(got.Edges), test.wantEdges)
|
||||
}
|
||||
if test.wantGraph != nil {
|
||||
if !reflect.DeepEqual(&got, test.wantGraph) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got.Nodes, test.wantGraph.Nodes)
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(got.Nodes[0], test.firstNode) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got.Nodes[0], test.firstNode)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Edges[0], test.firstEdge) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%v\nwant:\n%#v", test.path, got.Edges[0].Data.Source, test.firstEdge.Data.Source)
|
||||
}
|
||||
}
|
||||
if test.wantNodeAttributes != nil {
|
||||
var paths []string
|
||||
for _, n := range got.Nodes {
|
||||
paths = attrPaths(paths, "", n.Data.Attributes)
|
||||
}
|
||||
gotAttrs := make(map[string]bool)
|
||||
for _, p := range paths {
|
||||
gotAttrs[p] = true
|
||||
}
|
||||
if !reflect.DeepEqual(gotAttrs, test.wantNodeAttributes) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, gotAttrs, test.wantNodeAttributes)
|
||||
}
|
||||
}
|
||||
if test.wantEdgeAttributes != nil {
|
||||
var paths []string
|
||||
for _, e := range got.Edges {
|
||||
paths = attrPaths(paths, "", e.Data.Attributes)
|
||||
}
|
||||
gotAttrs := make(map[string]bool)
|
||||
for _, p := range paths {
|
||||
gotAttrs[p] = true
|
||||
}
|
||||
if !reflect.DeepEqual(gotAttrs, test.wantEdgeAttributes) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, gotAttrs, test.wantEdgeAttributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalNodeEdge(t *testing.T) {
|
||||
for _, test := range cytoscapejsNodeEdgeTests {
|
||||
data, err := ioutil.ReadFile(filepath.Join("testdata", test.path))
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var want Elements
|
||||
err = json.Unmarshal(data, &want)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
marshaled, err := json.Marshal(want)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var got Elements
|
||||
err = json.Unmarshal(marshaled, &got)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func attrPaths(dst []string, prefix string, m map[string]interface{}) []string {
|
||||
for k, v := range m {
|
||||
path := prefix
|
||||
if path != "" {
|
||||
path += "."
|
||||
}
|
||||
if v, ok := v.(map[string]interface{}); ok {
|
||||
dst = attrPaths(dst, path+k, v)
|
||||
}
|
||||
dst = append(dst, path+k)
|
||||
}
|
||||
return dst
|
||||
}
|
21
graph/formats/cytoscapejs/testdata/LICENSE
vendored
Normal file
21
graph/formats/cytoscapejs/testdata/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
Copyright (c) 2016-2018, The Cytoscape Consortium.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the “Software”), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
22
graph/formats/cytoscapejs/testdata/cola-compound.json
vendored
Normal file
22
graph/formats/cytoscapejs/testdata/cola-compound.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"nodes": [
|
||||
{ "data": { "id": "compound-1" } },
|
||||
{ "data": { "id": "compound-2" } },
|
||||
{ "data": { "id": "compound-3" } },
|
||||
{ "data": { "id": "b", "parent": "compound-1" } },
|
||||
{ "data": { "id": "c", "parent": "compound-1" } },
|
||||
{ "data": { "id": "a", "parent": "compound-2" } },
|
||||
{ "data": { "id": "d", "parent": "compound-3" } },
|
||||
{ "data": { "id": "e", "parent": "compound-3" } },
|
||||
{ "data": { "id": "f" } }
|
||||
],
|
||||
"edges": [
|
||||
{ "data": { "id": "ab", "source": "a", "target": "b" } },
|
||||
{ "data": { "id": "bc", "source": "b", "target": "c" } },
|
||||
{ "data": { "id": "ac", "source": "a", "target": "c" } },
|
||||
{ "data": { "id": "cd", "source": "c", "target": "d" } },
|
||||
{ "data": { "id": "de", "source": "d", "target": "e" } },
|
||||
{ "data": { "id": "df", "source": "d", "target": "f" } },
|
||||
{ "data": { "id": "af", "source": "a", "target": "f" } }
|
||||
]
|
||||
}
|
116
graph/formats/cytoscapejs/testdata/edge-type.json
vendored
Normal file
116
graph/formats/cytoscapejs/testdata/edge-type.json
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
[{
|
||||
"data": {
|
||||
"id": "n01",
|
||||
"type": "bezier"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n02"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e01",
|
||||
"source": "n01",
|
||||
"target": "n02"
|
||||
},
|
||||
"classes": "bezier"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e02",
|
||||
"source": "n01",
|
||||
"target": "n02"
|
||||
},
|
||||
"classes": "bezier"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e03",
|
||||
"source": "n02",
|
||||
"target": "n01"
|
||||
},
|
||||
"classes": "bezier"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n03",
|
||||
"type": "unbundled-bezier"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n04"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e04",
|
||||
"source": "n03",
|
||||
"target": "n04"
|
||||
},
|
||||
"classes": "unbundled-bezier"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n05",
|
||||
"type": "unbundled-bezier(multiple)"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n06"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e05",
|
||||
"source": "n05",
|
||||
"target": "n06"
|
||||
},
|
||||
"classes": "multi-unbundled-bezier"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n07",
|
||||
"type": "haystack"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n08"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e06",
|
||||
"source": "n08",
|
||||
"target": "n07"
|
||||
},
|
||||
"classes": "haystack"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e07",
|
||||
"source": "n08",
|
||||
"target": "n07"
|
||||
},
|
||||
"classes": "haystack"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e08",
|
||||
"source": "n08",
|
||||
"target": "n07"
|
||||
},
|
||||
"classes": "haystack"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e09",
|
||||
"source": "n08",
|
||||
"target": "n07"
|
||||
},
|
||||
"classes": "haystack"
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n09",
|
||||
"type": "segments"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "n10"
|
||||
}
|
||||
}, {
|
||||
"data": {
|
||||
"id": "e10",
|
||||
"source": "n09",
|
||||
"target": "n10"
|
||||
},
|
||||
"classes": "segments"
|
||||
}]
|
50322
graph/formats/cytoscapejs/testdata/tokyo-railways.json
vendored
Normal file
50322
graph/formats/cytoscapejs/testdata/tokyo-railways.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
128
graph/formats/sigmajs/sigmajs.go
Normal file
128
graph/formats/sigmajs/sigmajs.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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 sigmajs implements marshaling and unmarshaling of Sigma.js JSON documents.
|
||||
//
|
||||
// See http://sigmajs.org/ for Sigma.js documentation.
|
||||
package sigmajs // import "gonum.org/v1/gonum/graph/formats/sigmajs"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Graph is a Sigma.js graph.
|
||||
type Graph struct {
|
||||
Nodes []Node `json:"nodes"`
|
||||
Edges []Edge `json:"edges"`
|
||||
}
|
||||
|
||||
// Node is a Sigma.js node.
|
||||
type Node struct {
|
||||
ID string
|
||||
Attributes map[string]interface{}
|
||||
}
|
||||
|
||||
var (
|
||||
_ json.Marshaler = (*Node)(nil)
|
||||
_ json.Unmarshaler = (*Node)(nil)
|
||||
)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||
if n.Attributes == nil {
|
||||
type node struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
return json.Marshal(node{ID: n.ID})
|
||||
}
|
||||
n.Attributes["id"] = n.ID
|
||||
b, err := json.Marshal(n.Attributes)
|
||||
delete(n.Attributes, "id")
|
||||
return b, err
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (n *Node) 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("sigmajs: no ID")
|
||||
}
|
||||
n.ID = fmt.Sprint(id)
|
||||
delete(attrs, "id")
|
||||
if len(attrs) != 0 {
|
||||
n.Attributes = attrs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Edge is a Sigma.js edge.
|
||||
type Edge struct {
|
||||
ID string
|
||||
Source string
|
||||
Target string
|
||||
Attributes map[string]interface{}
|
||||
}
|
||||
|
||||
var (
|
||||
_ json.Marshaler = (*Edge)(nil)
|
||||
_ json.Unmarshaler = (*Edge)(nil)
|
||||
)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (e *Edge) 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 *Edge) 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("sigmajs: no ID")
|
||||
}
|
||||
source, ok := attrs["source"]
|
||||
if !ok {
|
||||
return errors.New("sigmajs: no source")
|
||||
}
|
||||
target, ok := attrs["target"]
|
||||
if !ok {
|
||||
return errors.New("sigmajs: 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
|
||||
}
|
334
graph/formats/sigmajs/sigmajs_test.go
Normal file
334
graph/formats/sigmajs/sigmajs_test.go
Normal file
@@ -0,0 +1,334 @@
|
||||
// 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 sigmajs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var sigmajsExampleTests = []struct {
|
||||
path string
|
||||
wantNodes int
|
||||
wantEdges int
|
||||
wantGraph *Graph
|
||||
wantAttributes map[string]bool
|
||||
}{
|
||||
{
|
||||
path: "geolocalized.json",
|
||||
wantNodes: 17,
|
||||
wantEdges: 35,
|
||||
wantGraph: &Graph{
|
||||
Nodes: []Node{
|
||||
{
|
||||
ID: "n1",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n1",
|
||||
"longitude": 2.48,
|
||||
"latitude": 50.93,
|
||||
"size": "5.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n2",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n2",
|
||||
"latitude": 50.88,
|
||||
"longitude": 2.0,
|
||||
"size": "5.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n4",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n4",
|
||||
"latitude": 49.4,
|
||||
"longitude": 0.19,
|
||||
"size": "6.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n5",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n5",
|
||||
"latitude": 48.49,
|
||||
"longitude": -1.92,
|
||||
"size": "6.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n6",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n6",
|
||||
"latitude": 48.26,
|
||||
"longitude": -4.38,
|
||||
"size": "4.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n7",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n7",
|
||||
"latitude": 47.15,
|
||||
"longitude": -2.09,
|
||||
"size": "6.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n8",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n8",
|
||||
"latitude": 46.02,
|
||||
"longitude": -1.04,
|
||||
"size": "6.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n9",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n9",
|
||||
"latitude": 43.22,
|
||||
"longitude": -1.85,
|
||||
"size": "5.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n10",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n10",
|
||||
"latitude": 42.38,
|
||||
"longitude": 3.18,
|
||||
"color": "rgb(1,179,255)",
|
||||
"size": "4.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n11",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n11",
|
||||
"latitude": 43.47,
|
||||
"longitude": 4.04,
|
||||
"size": "5.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n12",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n12",
|
||||
"latitude": 42.9,
|
||||
"longitude": 6.59,
|
||||
"size": "5.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n13",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n13",
|
||||
"latitude": 43.62,
|
||||
"longitude": 7.66,
|
||||
"size": "6.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n14",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n14",
|
||||
"latitude": 46.05,
|
||||
"longitude": 6.19,
|
||||
"size": "6.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n15",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n15",
|
||||
"latitude": 47.43,
|
||||
"longitude": 7.65,
|
||||
"size": "6.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n16",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n16",
|
||||
"latitude": 48.9,
|
||||
"longitude": 8.32,
|
||||
"size": "5.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "n17",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "n17",
|
||||
"latitude": 49.83,
|
||||
"longitude": 4.94,
|
||||
"size": "6.5",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "Paris",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Paris",
|
||||
"latitude": 48.72,
|
||||
"longitude": 2.46,
|
||||
"size": "9.0",
|
||||
"color": "rgb(1,179,255)",
|
||||
},
|
||||
},
|
||||
},
|
||||
Edges: []Edge{
|
||||
{ID: "8", Source: "n1", Target: "Paris"},
|
||||
{ID: "7", Source: "n2", Target: "n4"},
|
||||
{ID: "28", Source: "n4", Target: "n1"},
|
||||
{ID: "30", Source: "n4", Target: "n7"},
|
||||
{ID: "26", Source: "n5", Target: "n1"},
|
||||
{ID: "27", Source: "n5", Target: "n2"},
|
||||
{ID: "0", Source: "n6", Target: "n5"},
|
||||
{ID: "29", Source: "n7", Target: "n5"},
|
||||
{ID: "1", Source: "n7", Target: "n8"},
|
||||
{ID: "17", Source: "n7", Target: "Paris"},
|
||||
{ID: "10", Source: "n8", Target: "n13"},
|
||||
{ID: "18", Source: "n8", Target: "Paris"},
|
||||
{ID: "15", Source: "n9", Target: "n8"},
|
||||
{ID: "34", Source: "n10", Target: "n9"},
|
||||
{ID: "31", Source: "n10", Target: "n11"},
|
||||
{ID: "11", Source: "n11", Target: "n13"},
|
||||
{ID: "13", Source: "n11", Target: "n14"},
|
||||
{ID: "32", Source: "n12", Target: "n10"},
|
||||
{ID: "12", Source: "n12", Target: "n11"},
|
||||
{ID: "23", Source: "n12", Target: "n13"},
|
||||
{ID: "33", Source: "n13", Target: "n10"},
|
||||
{ID: "25", Source: "n13", Target: "n14"},
|
||||
{ID: "14", Source: "n14", Target: "n9"},
|
||||
{ID: "5", Source: "n14", Target: "n17"},
|
||||
{ID: "19", Source: "n14", Target: "Paris"},
|
||||
{ID: "6", Source: "n15", Target: "n8"},
|
||||
{ID: "22", Source: "n15", Target: "n16"},
|
||||
{ID: "20", Source: "n15", Target: "Paris"},
|
||||
{ID: "4", Source: "n16", Target: "n15"},
|
||||
{ID: "24", Source: "n16", Target: "Paris"},
|
||||
{ID: "9", Source: "n17", Target: "n7"},
|
||||
{ID: "21", Source: "n17", Target: "n17"},
|
||||
{ID: "2", Source: "Paris", Target: "n4"},
|
||||
{ID: "3", Source: "Paris", Target: "n17"},
|
||||
{ID: "16", Source: "Paris", Target: "Paris"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "arctic.json",
|
||||
wantNodes: 1715,
|
||||
wantEdges: 6676,
|
||||
wantAttributes: map[string]bool{
|
||||
"label": true,
|
||||
"x": true,
|
||||
"y": true,
|
||||
"color": true,
|
||||
"size": true,
|
||||
"attributes": true,
|
||||
"attributes.nodedef": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
for _, test := range sigmajsExampleTests {
|
||||
data, err := ioutil.ReadFile(filepath.Join("testdata", test.path))
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var got Graph
|
||||
err = json.Unmarshal(data, &got)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
if len(got.Nodes) != test.wantNodes {
|
||||
t.Errorf("unexpected result for order of %q: got:%d want:%d", test.path, len(got.Nodes), test.wantNodes)
|
||||
}
|
||||
if len(got.Edges) != test.wantEdges {
|
||||
t.Errorf("unexpected result for size of %q: got:%d want:%d", test.path, len(got.Edges), test.wantEdges)
|
||||
}
|
||||
if test.wantGraph != nil && !reflect.DeepEqual(&got, test.wantGraph) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got, test.wantGraph)
|
||||
}
|
||||
if test.wantAttributes != nil {
|
||||
var paths []string
|
||||
for _, n := range got.Nodes {
|
||||
paths = attrPaths(paths, "", n.Attributes)
|
||||
}
|
||||
gotAttrs := make(map[string]bool)
|
||||
for _, p := range paths {
|
||||
gotAttrs[p] = true
|
||||
}
|
||||
if !reflect.DeepEqual(gotAttrs, test.wantAttributes) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, gotAttrs, test.wantAttributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func attrPaths(dst []string, prefix string, m map[string]interface{}) []string {
|
||||
for k, v := range m {
|
||||
path := prefix
|
||||
if path != "" {
|
||||
path += "."
|
||||
}
|
||||
if v, ok := v.(map[string]interface{}); ok {
|
||||
dst = attrPaths(dst, path+k, v)
|
||||
}
|
||||
dst = append(dst, path+k)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, test := range sigmajsExampleTests {
|
||||
data, err := ioutil.ReadFile(filepath.Join("testdata", test.path))
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var want Graph
|
||||
err = json.Unmarshal(data, &want)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
marshaled, err := json.Marshal(want)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
var got Graph
|
||||
err = json.Unmarshal(marshaled, &got)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal %q: %v", test.path, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected result for %q:\ngot:\n%#v\nwant:\n%#v", test.path, got, want)
|
||||
}
|
||||
}
|
||||
}
|
12
graph/formats/sigmajs/testdata/LICENSE.txt
vendored
Normal file
12
graph/formats/sigmajs/testdata/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
Copyright (C) 2013-2014, Alexis Jacomy, http://sigmajs.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
1
graph/formats/sigmajs/testdata/arctic.json
vendored
Normal file
1
graph/formats/sigmajs/testdata/arctic.json
vendored
Normal file
File diff suppressed because one or more lines are too long
317
graph/formats/sigmajs/testdata/geolocalized.json
vendored
Normal file
317
graph/formats/sigmajs/testdata/geolocalized.json
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
{
|
||||
"nodes":[
|
||||
{
|
||||
"id":"n1",
|
||||
"label":"n1",
|
||||
"latitude":50.93,
|
||||
"longitude":2.48,
|
||||
"size":"5.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n2",
|
||||
"label":"n2",
|
||||
"latitude":50.88,
|
||||
"longitude":2.0,
|
||||
"size":"5.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n4",
|
||||
"label":"n4",
|
||||
"latitude":49.4,
|
||||
"longitude":0.19,
|
||||
"size":"6.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n5",
|
||||
"label":"n5",
|
||||
"latitude":48.49,
|
||||
"longitude":-1.92,
|
||||
"size":"6.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n6",
|
||||
"label":"n6",
|
||||
"latitude":48.26,
|
||||
"longitude":-4.38,
|
||||
"size":"4.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n7",
|
||||
"label":"n7",
|
||||
"latitude":47.15,
|
||||
"longitude":-2.09,
|
||||
"size":"6.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n8",
|
||||
"label":"n8",
|
||||
"latitude":46.02,
|
||||
"longitude":-1.04,
|
||||
"size":"6.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n9",
|
||||
"label":"n9",
|
||||
"latitude":43.22,
|
||||
"longitude":-1.85,
|
||||
"size":"5.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n10",
|
||||
"label":"n10",
|
||||
"latitude":42.38,
|
||||
"longitude":3.18,
|
||||
"size":"4.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n11",
|
||||
"label":"n11",
|
||||
"latitude":43.47,
|
||||
"longitude":4.04,
|
||||
"size":"5.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n12",
|
||||
"label":"n12",
|
||||
"latitude":42.9,
|
||||
"longitude":6.59,
|
||||
"size":"5.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n13",
|
||||
"label":"n13",
|
||||
"latitude":43.62,
|
||||
"longitude":7.66,
|
||||
"size":"6.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n14",
|
||||
"label":"n14",
|
||||
"latitude":46.05,
|
||||
"longitude":6.19,
|
||||
"size":"6.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n15",
|
||||
"label":"n15",
|
||||
"latitude":47.43,
|
||||
"longitude":7.65,
|
||||
"size":"6.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n16",
|
||||
"label":"n16",
|
||||
"latitude":48.9,
|
||||
"longitude":8.32,
|
||||
"size":"5.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"n17",
|
||||
"label":"n17",
|
||||
"latitude":49.83,
|
||||
"longitude":4.94,
|
||||
"size":"6.5",
|
||||
"color":"rgb(1,179,255)"
|
||||
},
|
||||
{
|
||||
"id":"Paris",
|
||||
"label":"Paris",
|
||||
"latitude":48.72,
|
||||
"longitude":2.46,
|
||||
"size":"9.0",
|
||||
"color":"rgb(1,179,255)"
|
||||
}
|
||||
],
|
||||
"edges":[
|
||||
{
|
||||
"id":"8",
|
||||
"source":"n1",
|
||||
"target":"Paris"
|
||||
},
|
||||
{
|
||||
"id":"7",
|
||||
"source":"n2",
|
||||
"target":"n4"
|
||||
},
|
||||
{
|
||||
"id":"28",
|
||||
"source":"n4",
|
||||
"target":"n1"
|
||||
},
|
||||
{
|
||||
"id":"30",
|
||||
"source":"n4",
|
||||
"target":"n7"
|
||||
},
|
||||
{
|
||||
"id":"26",
|
||||
"source":"n5",
|
||||
"target":"n1"
|
||||
},
|
||||
{
|
||||
"id":"27",
|
||||
"source":"n5",
|
||||
"target":"n2"
|
||||
},
|
||||
{
|
||||
"id":"0",
|
||||
"source":"n6",
|
||||
"target":"n5"
|
||||
},
|
||||
{
|
||||
"id":"29",
|
||||
"source":"n7",
|
||||
"target":"n5"
|
||||
},
|
||||
{
|
||||
"id":"1",
|
||||
"source":"n7",
|
||||
"target":"n8"
|
||||
},
|
||||
{
|
||||
"id":"17",
|
||||
"source":"n7",
|
||||
"target":"Paris"
|
||||
},
|
||||
{
|
||||
"id":"10",
|
||||
"source":"n8",
|
||||
"target":"n13"
|
||||
},
|
||||
{
|
||||
"id":"18",
|
||||
"source":"n8",
|
||||
"target":"Paris"
|
||||
},
|
||||
{
|
||||
"id":"15",
|
||||
"source":"n9",
|
||||
"target":"n8"
|
||||
},
|
||||
{
|
||||
"id":"34",
|
||||
"source":"n10",
|
||||
"target":"n9"
|
||||
},
|
||||
{
|
||||
"id":"31",
|
||||
"source":"n10",
|
||||
"target":"n11"
|
||||
},
|
||||
{
|
||||
"id":"11",
|
||||
"source":"n11",
|
||||
"target":"n13"
|
||||
},
|
||||
{
|
||||
"id":"13",
|
||||
"source":"n11",
|
||||
"target":"n14"
|
||||
},
|
||||
{
|
||||
"id":"32",
|
||||
"source":"n12",
|
||||
"target":"n10"
|
||||
},
|
||||
{
|
||||
"id":"12",
|
||||
"source":"n12",
|
||||
"target":"n11"
|
||||
},
|
||||
{
|
||||
"id":"23",
|
||||
"source":"n12",
|
||||
"target":"n13"
|
||||
},
|
||||
{
|
||||
"id":"33",
|
||||
"source":"n13",
|
||||
"target":"n10"
|
||||
},
|
||||
{
|
||||
"id":"25",
|
||||
"source":"n13",
|
||||
"target":"n14"
|
||||
},
|
||||
{
|
||||
"id":"14",
|
||||
"source":"n14",
|
||||
"target":"n9"
|
||||
},
|
||||
{
|
||||
"id":"5",
|
||||
"source":"n14",
|
||||
"target":"n17"
|
||||
},
|
||||
{
|
||||
"id":"19",
|
||||
"source":"n14",
|
||||
"target":"Paris"
|
||||
},
|
||||
{
|
||||
"id":"6",
|
||||
"source":"n15",
|
||||
"target":"n8"
|
||||
},
|
||||
{
|
||||
"id":"22",
|
||||
"source":"n15",
|
||||
"target":"n16"
|
||||
},
|
||||
{
|
||||
"id":"20",
|
||||
"source":"n15",
|
||||
"target":"Paris"
|
||||
},
|
||||
{
|
||||
"id":"4",
|
||||
"source":"n16",
|
||||
"target":"n15"
|
||||
},
|
||||
{
|
||||
"id":"24",
|
||||
"source":"n16",
|
||||
"target":"Paris"
|
||||
},
|
||||
{
|
||||
"id":"9",
|
||||
"source":"n17",
|
||||
"target":"n7"
|
||||
},
|
||||
{
|
||||
"id":"21",
|
||||
"source":"n17",
|
||||
"target":"n17"
|
||||
},
|
||||
{
|
||||
"id":"2",
|
||||
"source":"Paris",
|
||||
"target":"n4"
|
||||
},
|
||||
{
|
||||
"id":"3",
|
||||
"source":"Paris",
|
||||
"target":"n17"
|
||||
},
|
||||
{
|
||||
"id":"16",
|
||||
"source":"Paris",
|
||||
"target":"Paris"
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user