Giter VIP home page Giter VIP logo

Comments (4)

hexaeder avatar hexaeder commented on July 1, 2024

It seems like what they call :stress refers to another function by_axis_local_stress_graph which is not taken from NetworkLayout.

I am not sure how those two relate as I am not familiar with the inner workings of the Stress layout. But they reference "section 2.3 from http://link.springer.com/chapter/10.1007%2F978-3-540-31843-9_25#page-1" as a source. It would be cool to add that to NetworkLayout.

from graphmakie.jl.

hexaeder avatar hexaeder commented on July 1, 2024

In case you're interested: here is a quick&dirty port of those functions for GraphMakie.

using Pkg
Pkg.activate(temp=true)
Pkg.add("GLMakie")
Pkg.add("LightGraphs")
Pkg.add("GraphMakie")

using GLMakie
using LightGraphs
using GraphMakie
using GraphMakie.NetworkLayout
using SparseArrays

adj_matrix = [0 1 1 0 0 0 0 0 0 0;
              0 0 0 0 1 1 0 0 0 0;
              0 0 0 1 0 0 1 0 1 0;
              0 0 0 0 0 0 0 0 0 0;
              0 0 0 0 0 0 0 1 0 1;
              0 0 0 0 0 0 0 0 0 0;
              0 0 0 0 0 0 0 0 0 0;
              0 0 0 0 0 0 0 0 0 0;
              0 0 0 0 0 0 0 0 0 0;
              0 0 0 0 0 0 0 0 0 0]

g = SimpleGraph(SimpleDiGraph(adj_matrix))
graphplot(g; layout=Stress())

# code copied and adapted from GraphRecipes
# https://github.com/JuliaPlots/GraphRecipes.jl/blob/5559436a981089eb842f4c7b7a96a4dbf9abe466/src/graph_layouts.jl#L157-L204
function by_axis_local_stress_graph(g)
    adjmat = adjacency_matrix(g)

    # parameters
    node_weights = ones(size(adjmat,1))
    dim = 2
    free_dims = 1:dim
    x = rand(length(node_weights))
    y = rand(length(node_weights))
    z = rand(length(node_weights))
    maxiter = 1000

    n = length(node_weights)

    # graph-theoretical distance between node i and j (i.e. shortest path distance)
    # TODO: calculate a real distance
    dist = estimate_distance(adjmat)
    # @show dist

    # also known as kᵢⱼ in "axis-by-axis stress minimization".  the -2 could also be 0 or -1?
    w = dist .^ -2

    # in each iteration, we update one dimension/node at a time, reducing the total stress with each update
    X = dim == 2 ? (x, y) : (x, y, z)
    laststress = stressf(X, dist, w)
    for k in 1:maxiter
        for p in free_dims
            for i=1:n
                numer, denom = 0.0, 0.0
                for j=1:n
                    i==j && continue
                    numer += w[i,j] * (X[p][j] + dist[i,j] * (X[p][i] - X[p][j]) / norm_ij(X, i, j))
                    denom += w[i,j]
                end
                if denom != 0
                    X[p][i] = numer / denom
                end
            end
        end

        # check for convergence of the total stress
        thisstress = stressf(X, dist, w)
        if abs(thisstress - laststress) / abs(laststress) < 1e-6
            # info("converged. numiter=$k last=$laststress this=$thisstress")
            break
        end
        laststress = thisstress
    end

    GraphMakie.Pointf.(zip(X...))
end

function estimate_distance(adjmat::AbstractMatrix)
    g = LightGraphs.Graph(adjmat)
    dists = convert(Matrix{Float64}, hcat(map(i->LightGraphs.dijkstra_shortest_paths(g, i).dists, LightGraphs.vertices(g))...))
    tot = 0.0; cnt = 0
    for (i,d) in enumerate(dists)
        if d < 1e10
            tot += d
            cnt += 1
        end
    end
    avg = cnt > 0 ? tot / cnt : 1.0
    for (i,d) in enumerate(dists)
        if d > 1e10
            dists[i] = 3avg
        end
    end
    dists
end

norm_ij(X, i, j) = sqrt(sum(Float64[(v[i]-v[j])^2 for v in X]))
stressf(X, dist, w, i, j) = w[i,j] * (norm_ij(X, i, j) - dist[i,j])^2
function stressf(X, dist, w)
    tot = 0.0
    for i=1:size(X,1), j=1:i-1
        tot += stressf(X, dist, w, i, j)
    end
    tot
end

graphplot(g; layout=by_axis_local_stress_graph)

from graphmakie.jl.

mroavi avatar mroavi commented on July 1, 2024

@hexaeder thanks for the explanations. Indeed, it would be really nice to have this network layout integrated into NetwrokLayout.jl so that it can be used from GraphMakie.jl. So far, it has been the one that works best for all my graphs. Thanks also for the "quick&dirty" port. I'll use this for now. I'll change the title to a feat request.

from graphmakie.jl.

mroavi avatar mroavi commented on July 1, 2024

I figured it was better to move this to NetworkLayout.jl JuliaGraphs/NetworkLayout.jl#39. I'll close this for now.

from graphmakie.jl.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.