Move NetworkSelector to widgets
This commit is contained in:
parent
1a6f3b1366
commit
2064fa5201
45
graph.go
45
graph.go
|
@ -1,12 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/divan/graphx/graph"
|
||||
"github.com/gopherjs/vecty"
|
||||
)
|
||||
|
||||
|
@ -41,44 +37,3 @@ func (p *Page) ApplyForces() {
|
|||
p.webgl.updatePositions()
|
||||
p.webgl.rt.Disable()
|
||||
}
|
||||
|
||||
// GraphFromJSON is a custom version of graphx JSON importer, as we want to use
|
||||
// some additional fields (Description).
|
||||
// TODO(divan): that's probably can be done better within the limits of graphx library.
|
||||
func GraphFromJSON(r io.Reader) (*graph.Graph, string, error) {
|
||||
// decode into temporary struct to process
|
||||
var res struct {
|
||||
Description string `json:"description"`
|
||||
Nodes []*graph.BasicNode `json:"nodes"`
|
||||
Links []*struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
} `json:"links"`
|
||||
}
|
||||
err := json.NewDecoder(r).Decode(&res)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(res.Nodes) == 0 {
|
||||
return nil, "", errors.New("empty graph")
|
||||
}
|
||||
|
||||
// convert links IDs into indices
|
||||
g := graph.NewGraphMN(len(res.Nodes), len(res.Links))
|
||||
|
||||
for _, node := range res.Nodes {
|
||||
g.AddNode(node)
|
||||
}
|
||||
|
||||
for _, link := range res.Links {
|
||||
err := g.AddLink(link.Source, link.Target)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
g.UpdateCache()
|
||||
|
||||
return g, res.Description, nil
|
||||
}
|
||||
|
|
70
network.go
70
network.go
|
@ -1,70 +0,0 @@
|
|||
//go:generate go-bindata data/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/divan/graphx/graph"
|
||||
)
|
||||
|
||||
// Network represents network graph and information, used for
|
||||
// for simulation and visualization.
|
||||
type Network struct {
|
||||
Name string
|
||||
Description string
|
||||
Data *graph.Graph
|
||||
}
|
||||
|
||||
// LoadNetwork loads network information from the JSON file.
|
||||
// JSON format is specified in graphx/formats package.
|
||||
func LoadNetwork(file string) (*Network, error) {
|
||||
content, err := Asset(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open bindata '%s': %v", file, err)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(content)
|
||||
|
||||
n, err := LoadNetworkFromReader(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open file '%s': %v", file, err)
|
||||
}
|
||||
n.Name = file
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// LoadNetworkFromReader loads network information from the io.Reader.
|
||||
func LoadNetworkFromReader(r io.Reader) (*Network, error) {
|
||||
g, desc, err := GraphFromJSON(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse JSON: %v", err)
|
||||
}
|
||||
|
||||
return &Network{
|
||||
Description: desc,
|
||||
Data: g,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String implements Stringer for Network.
|
||||
func (n *Network) String() string {
|
||||
return fmt.Sprintf("[%s: %s] - %d nodes, %d links", n.Name, n.Description, n.NodesCount(), n.LinksCount())
|
||||
}
|
||||
|
||||
// NodesCount returns number of the nodes in the network.
|
||||
func (n *Network) NodesCount() int {
|
||||
if n.Data == nil {
|
||||
return 0
|
||||
}
|
||||
return len(n.Data.Nodes())
|
||||
}
|
||||
|
||||
// LinksCount returns number of the links in the network.
|
||||
func (n *Network) LinksCount() int {
|
||||
if n.Data == nil {
|
||||
return 0
|
||||
}
|
||||
return len(n.Data.Links())
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
// data/net300.json
|
||||
// DO NOT EDIT!
|
||||
|
||||
package main
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
|
@ -0,0 +1,132 @@
|
|||
//go:generate go-bindata -pkg network data/
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/divan/graphx/graph"
|
||||
)
|
||||
|
||||
// Network represents network graph and information, used for
|
||||
// for simulation and visualization.
|
||||
type Network struct {
|
||||
Name string
|
||||
Description string
|
||||
Data *graph.Graph
|
||||
}
|
||||
|
||||
// LoadNetwork loads network information from the JSON file.
|
||||
// JSON format is specified in graphx/formats package.
|
||||
func LoadNetwork(file string) (*Network, error) {
|
||||
content, err := Asset(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open bindata '%s': %v", file, err)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(content)
|
||||
|
||||
n, err := LoadNetworkFromReader(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open file '%s': %v", file, err)
|
||||
}
|
||||
n.Name = file
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// LoadNetworks imports preloaded neworks from the directory with JSON files.
|
||||
func LoadNetworks() (map[string]*Network, error) {
|
||||
files, err := AssetDir("data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networks := map[string]*Network{}
|
||||
for _, file := range files {
|
||||
network, err := LoadNetwork("data/" + file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load network: %v", err)
|
||||
}
|
||||
|
||||
networks[file] = network
|
||||
}
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// LoadNetworkFromReader loads network information from the io.Reader.
|
||||
func LoadNetworkFromReader(r io.Reader) (*Network, error) {
|
||||
g, desc, err := GraphFromJSON(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse JSON: %v", err)
|
||||
}
|
||||
|
||||
return &Network{
|
||||
Description: desc,
|
||||
Data: g,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String implements Stringer for Network.
|
||||
func (n *Network) String() string {
|
||||
return fmt.Sprintf("[%s: %s] - %d nodes, %d links", n.Name, n.Description, n.NodesCount(), n.LinksCount())
|
||||
}
|
||||
|
||||
// NodesCount returns number of the nodes in the network.
|
||||
func (n *Network) NodesCount() int {
|
||||
if n.Data == nil {
|
||||
return 0
|
||||
}
|
||||
return len(n.Data.Nodes())
|
||||
}
|
||||
|
||||
// LinksCount returns number of the links in the network.
|
||||
func (n *Network) LinksCount() int {
|
||||
if n.Data == nil {
|
||||
return 0
|
||||
}
|
||||
return len(n.Data.Links())
|
||||
}
|
||||
|
||||
// GraphFromJSON is a custom version of graphx JSON importer, as we want to use
|
||||
// some additional fields (Description).
|
||||
// TODO(divan): that's probably can be done better within the limits of graphx library.
|
||||
func GraphFromJSON(r io.Reader) (*graph.Graph, string, error) {
|
||||
// decode into temporary struct to process
|
||||
var res struct {
|
||||
Description string `json:"description"`
|
||||
Nodes []*graph.BasicNode `json:"nodes"`
|
||||
Links []*struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
} `json:"links"`
|
||||
}
|
||||
err := json.NewDecoder(r).Decode(&res)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(res.Nodes) == 0 {
|
||||
return nil, "", errors.New("empty graph")
|
||||
}
|
||||
|
||||
// convert links IDs into indices
|
||||
g := graph.NewGraphMN(len(res.Nodes), len(res.Links))
|
||||
|
||||
for _, node := range res.Nodes {
|
||||
g.AddNode(node)
|
||||
}
|
||||
|
||||
for _, link := range res.Links {
|
||||
err := g.AddLink(link.Source, link.Target)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
g.UpdateCache()
|
||||
|
||||
return g, res.Description, nil
|
||||
}
|
7
page.go
7
page.go
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/gopherjs/vecty/elem"
|
||||
"github.com/gopherjs/vecty/event"
|
||||
"github.com/gopherjs/vecty/prop"
|
||||
"github.com/status-im/whispervis/network"
|
||||
"github.com/status-im/whispervis/widgets"
|
||||
)
|
||||
|
||||
|
@ -24,7 +25,7 @@ type Page struct {
|
|||
|
||||
loader *widgets.Loader
|
||||
forceEditor *widgets.ForceEditor
|
||||
network *NetworkSelector
|
||||
network *widgets.NetworkSelector
|
||||
simulationWidget *widgets.Simulation
|
||||
statsWidget *widgets.Stats
|
||||
|
||||
|
@ -37,7 +38,7 @@ func NewPage() *Page {
|
|||
loader: widgets.NewLoader(),
|
||||
}
|
||||
page.forceEditor = widgets.NewForceEditor(page.onForcesApply)
|
||||
page.network = NewNetworkSelector(page.onNetworkChange)
|
||||
page.network = widgets.NewNetworkSelector(page.onNetworkChange)
|
||||
page.webgl = NewWebGLScene()
|
||||
page.simulationWidget = widgets.NewSimulation("http://localhost:8084", page.startSimulation, page.replaySimulation)
|
||||
page.statsWidget = widgets.NewStats()
|
||||
|
@ -120,7 +121,7 @@ func (p *Page) onForcesApply() {
|
|||
p.UpdateGraph()
|
||||
}
|
||||
|
||||
func (p *Page) onNetworkChange(network *Network) {
|
||||
func (p *Page) onNetworkChange(network *network.Network) {
|
||||
fmt.Println("Network changed:", network)
|
||||
config := p.forceEditor.Config()
|
||||
p.layout = layout.New(network.Data, config.Config)
|
||||
|
|
|
@ -42,7 +42,7 @@ func (p *Page) runSimulation(address string) (*Simulation, error) {
|
|||
|
||||
// currentNetworkJSON returns JSON encoded description of the current graph/network.
|
||||
func (p *Page) currentNetworkJSON() []byte {
|
||||
net := p.network.current.Data
|
||||
net := p.network.Current().Data
|
||||
var buf bytes.Buffer
|
||||
err := formats.NewD3JSON(&buf, true).ExportGraph(net)
|
||||
if err != nil {
|
||||
|
|
2
stats.go
2
stats.go
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
func (p *Page) RecalculateStats(plog *propagation.Log) *stats.Stats {
|
||||
net := p.network.current
|
||||
net := p.network.Current()
|
||||
nodes := len(net.Data.Nodes())
|
||||
links := len(net.Data.Links())
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/gopherjs/vecty"
|
||||
"github.com/gopherjs/vecty/elem"
|
||||
"github.com/gopherjs/vecty/event"
|
||||
"github.com/status-im/whispervis/widgets"
|
||||
"github.com/status-im/whispervis/network"
|
||||
)
|
||||
|
||||
// DefaultNetwork specifies the network shown by default when app is first opened.
|
||||
|
@ -16,23 +16,22 @@ const DefaultNetwork = "grid25.json"
|
|||
|
||||
// NetworkSelector represents widget for choosing or uploading network topology
|
||||
// to be used for visualization.
|
||||
// TODO: move to widgets package
|
||||
type NetworkSelector struct {
|
||||
vecty.Core
|
||||
|
||||
current *Network
|
||||
current *network.Network
|
||||
isCustom bool
|
||||
networks map[string]*Network
|
||||
networks map[string]*network.Network
|
||||
|
||||
upload *widgets.UploadWidget
|
||||
upload *UploadWidget
|
||||
|
||||
handler func(*Network) // executed on network change
|
||||
handler func(*network.Network) // executed on network change
|
||||
}
|
||||
|
||||
// NewNetworkSelector creates new NetworkSelector.
|
||||
func NewNetworkSelector(handler func(*Network)) *NetworkSelector {
|
||||
current := &Network{}
|
||||
networks, err := LoadNetworks()
|
||||
func NewNetworkSelector(handler func(*network.Network)) *NetworkSelector {
|
||||
current := &network.Network{}
|
||||
networks, err := network.LoadNetworks()
|
||||
if err != nil {
|
||||
fmt.Println("No networks loaded:", err)
|
||||
} else {
|
||||
|
@ -44,15 +43,15 @@ func NewNetworkSelector(handler func(*Network)) *NetworkSelector {
|
|||
current: current,
|
||||
handler: handler,
|
||||
}
|
||||
ns.upload = widgets.NewUploadWidget(ns.onUpload)
|
||||
ns.upload = NewUploadWidget(ns.onUpload)
|
||||
ns.setCurrentNetwork(current)
|
||||
return ns
|
||||
}
|
||||
|
||||
// Render implements the vecty.Component interface.
|
||||
func (n *NetworkSelector) Render() vecty.ComponentOrHTML {
|
||||
return widgets.Widget(
|
||||
widgets.Header("Network graph:"),
|
||||
return Widget(
|
||||
Header("Network graph:"),
|
||||
elem.Div(
|
||||
vecty.Markup(
|
||||
vecty.Class("select", "is-fullwidth"),
|
||||
|
@ -94,25 +93,6 @@ func (n *NetworkSelector) descriptionBlock() *vecty.HTML {
|
|||
)
|
||||
}
|
||||
|
||||
// LoadNetworks imports preloaded neworks from the directory with JSON files.
|
||||
func LoadNetworks() (map[string]*Network, error) {
|
||||
files, err := AssetDir("data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networks := map[string]*Network{}
|
||||
for _, file := range files {
|
||||
network, err := LoadNetwork("data/" + file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load network: %v", err)
|
||||
}
|
||||
|
||||
networks[file] = network
|
||||
}
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// networkOptions renders 'option' elements for network 'select' input tag.
|
||||
func (n *NetworkSelector) networkOptions() vecty.List {
|
||||
var options vecty.List
|
||||
|
@ -149,7 +129,7 @@ func (n *NetworkSelector) onChange(e *vecty.Event) {
|
|||
// onUpload implements callback for "Upload" button clicked event.
|
||||
func (n *NetworkSelector) onUpload(json []byte) {
|
||||
r := bytes.NewReader(json)
|
||||
net, err := LoadNetworkFromReader(r)
|
||||
net, err := network.LoadNetworkFromReader(r)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Load network: %v", err)
|
||||
}
|
||||
|
@ -162,8 +142,13 @@ func (n *NetworkSelector) onUpload(json []byte) {
|
|||
vecty.Rerender(n)
|
||||
}
|
||||
|
||||
// Current returns the currently selected network.
|
||||
func (n *NetworkSelector) Current() *network.Network {
|
||||
return n.current
|
||||
}
|
||||
|
||||
// setCurrentNetwork changes current network and runs needed update handlers.
|
||||
func (n *NetworkSelector) setCurrentNetwork(net *Network) {
|
||||
func (n *NetworkSelector) setCurrentNetwork(net *network.Network) {
|
||||
n.current = net
|
||||
|
||||
if n.handler != nil {
|
Loading…
Reference in New Issue