Graphe pondéré lumineux (réseau): sommets et arêtes

Aug 19 2020

J'essaie de trouver un moyen (de préférence simple et performances / vitesse optimisées pour les graphiques plus grands) de faire ce qui suit:

  • Styliser les sommets du graphe par effet lumineux et son intensité en fonction de VertexWeight

  • Styliser les bords du graphique par effet lumineux et son intensité en fonction de EdgeWeight

  • DirectedEdgeUn style à effet lumineux est également souhaitable (alors que pour plus de simplicité, les choses peuvent commencer UndirectedEdge)

Par exemple pour quelque chose comme ça:

RandomGraph[{20,100},
VertexWeight->RandomReal[1,20],
EdgeWeight->RandomReal[1,100],
Background->Black,
BaseStyle->White]

Je recherche un visuel similaire à celui ci-dessous, sauf que les bords doivent aussi briller:

Les problèmes que je rencontre.

1. Mise en œuvre simple d'une lueur époustouflante

J'ai vu divers effets de brillance (y compris CECI sur les points lumineux) mais je ne suis pas un expert des meilleures idées visuelles vs performances. Étonnamment aussi, je n'ai pas vu grand-chose sur les lignes brillantes autour. Je commencerais naïvement par quelque chose comme ça, mais cela peut probablement être amélioré visuellement et en termes de performances:

bsc=BSplineCurve[{{0,0},{1,1},{2,0}}];
Graphics[
    Table[{White,Opacity[1/k^1.2],Thickness[.005k],CapForm["Round"],bsc},{k,20}],
Background->Black]

2. Passer des poids pour briller

Bien que je sache VertexShapeFunctionet EdgeShapeFunction, je ne sais pas trop comment leur transmettre de manière optimale les poids ... et si ces propriétés sont la bonne approche.

Glow dans les fonctions intégrées

J'ai remarqué que ces fonctions produisent de la lueur:

ComplexPlot[z^2+1,{z,-2-2I,2+2I},ColorFunction->"CyclicReImLogAbs"]

Et comme l'a remarqué @EC dans sa réponse ci-dessous, quelque chose comme

ImageAdjust[DistanceTransform[Graphics[Point[RandomReal[1,{100,2}]]]]]

Merci, votre aide est très appréciée!

Réponses

14 flinty Aug 19 2020 at 22:41

Vous pouvez obtenir un effet d'éclat global en ImageAddutilisant une copie floue du masque d'image. Certes, c'est un peu basique, mais l'effet est convaincant. J'ai choisi de créer un réseau `` cérébral '' en utilisant AnatomyDataet NearestNeighbourGraphde le faire ressembler à une chose de marketing IA surexcitée:

SeedRandom[123];
brain = AnatomyData[Entity["AnatomicalStructure", "Brain"], "MeshRegion"];
boundary = RegionBoundary[brain];
nng = NearestNeighborGraph[RandomPoint[boundary, 1000], 7];
brainnetimg = Rasterize[
   GraphPlot3D[nng, ViewPoint -> Left, 
    VertexStyle -> Directive[AbsolutePointSize[7], White], 
    EdgeStyle -> Directive[AbsoluteThickness[2], White], 
    Background -> Black]
   , ImageSize -> 1000];
ImageAdd[ImageAdjust[Blur[Binarize@brainnetimg, 7], .1], 
 ImageMultiply[brainnetimg, 
  LinearGradientImage[{Blue, Cyan, Purple}, 
   ImageDimensions[brainnetimg]]]]

Pour que les poids affectent la taille de la lueur, vous devrez probablement utiliser EdgeShapeFunctionet VertexShapeFunction. J'ai créé une texture de panneau d'affichage d'un effet de lentille avec alpha et j'ai utilisé cette image pour les sommets:

J'ai également utilisé l'effet de lueur de bord que vous avez mentionné dans la question qui empile les lignes. Les arêtes avec plus de poids devraient avoir plus de brillance, et les sommets avec plus de poids auront un plus grand évasement:

SeedRandom[123];
G = SpatialGraphDistribution[100, 0.20];
g = RandomGraph[G];
glowtexture = Import["lensbb.png"];
edgeWeights = RandomReal[1, EdgeCount[g]];
vertexWeights = RandomReal[1, VertexCount[g]];

edgeShapeFunc = 
  With[{weight = AnnotationValue[{g, #2}, EdgeWeight]}, 
    Table[{RGBColor[0.7, 1.0, 0.9], Opacity[1/k^1.3], 
      Thickness[.001 k*weight], CapForm["Round"], Line[#1]}, {k, 20}]] &;

vertexShapeFunc = 
  With[{weight = AnnotationValue[{g, #2}, VertexWeight]}, 
    Inset[glowtexture, #1, Center, weight*0.3]] &;

g = Graph[g, EdgeWeight -> edgeWeights, VertexWeight -> vertexWeights,
   VertexShapeFunction -> vertexShapeFunc, Background -> Black, 
  EdgeShapeFunction -> edgeShapeFunc, PlotRangePadding -> .1]

Plutôt que d'utiliser l'astuce d'empilement / opacité de lignes ci-dessus pour produire les bords brillants, vous pouvez également utiliser des polygones texturés à la place. C'est plus rapide mais un inconvénient est que lorsque les bords deviennent trop épais, les bouchons sont visibles et laids:

g = Graph[UndirectedEdge @@@ {{1, 2}, {2, 3}, {3, 1}}];
edgeWeights = {1, 2, 3}/6.;
vertexWeights = {1, 2, 3}/6.;

glowtexture = Import["lensbb.png"];
edgegradimg = LinearGradientImage[{Transparent,Cyan,Transparent}, {64,64}];

edgeShapeFunc = 
  Module[{weight = AnnotationValue[{g, #2}, EdgeWeight], s = 1/10., 
     vec = #1[[2]] - #1[[1]], perp},
    perp = Cross[vec];
    {Texture[edgegradimg], 
     Polygon[{
         #1[[1]]-perp*weight*s, 
         #1[[1]]+perp*weight*s,
         #1[[2]]+perp*weight*s,
         #1[[2]]-perp*weight*s
     }, VertexTextureCoordinates -> {{0,0},{1,0},{1,1},{0,1}}]
    }] &;

vertexShapeFunc = 
  With[{weight = AnnotationValue[{g, #2}, VertexWeight]}, 
    Inset[glowtexture, #1, Center, weight*3]] &;

g = Graph[g, EdgeWeight -> edgeWeights, VertexWeight -> vertexWeights,
   VertexShapeFunction -> vertexShapeFunc, Background -> Black, 
  EdgeShapeFunction -> edgeShapeFunc, PlotRangePadding -> .5]

8 C.E. Aug 19 2020 at 14:37

DistanceTransform nous donne une carte de distance du type dont nous avons besoin pour briller.

Nous définissons d'abord la source lumineuse:

bg = ConstantImage[White, 200];
line = HighlightImage[
  bg, {
   Black,
   Thick,
   Line[{{50, 100}, {150, 100}}]
   }]

Ensuite, nous calculons la transformation de distance. Nous la redimensionnons de telle sorte que 1 dans l'image résultante corresponde à la diagonale de l'image.

glow = ColorNegate@Image[Divide[
     ImageData@DistanceTransform[line],
     200 Sqrt[2]
     ]^0.2]

Le nombre 0,2 contrôle la rapidité avec laquelle la lueur s'éteint.

Ensuite, nous pouvons appliquer une couleur à la lueur:

glow ConstantImage[Red, 200]

Et nous pouvons même appliquer des fonctions de couleur:

ImageApply[List @@ ColorData["AvocadoColors", #] &, glow]

Créer une belle fonction de couleur sera la clé pour créer une belle lueur comme celle de votre exemple.

Créer un graphique brillant est assez simple en utilisant cette technique. Chaque arête est une ligne et chaque sommet est un point ou un disque. En fin de compte, nous pouvons les rassembler en une seule image.

Je vais laisser au lecteur le soin de créer une fonction robuste pour cela. Je vais juste faire un petit exemple.

Nous utiliserons le graphe Pappus pour l'exemple:

embedding = First@GraphData["PappusGraph", "Embeddings"];
coords = List @@@ GraphData["PappusGraph", "Edges"] /. Thread[
    Range[Length[embedding]] -> embedding
    ];
Graphics[{
  Point[embedding],
  Line[coords]
  }]

Le dessiner sur une image plutôt que dans un graphique nécessite de redimensionner les coordonnées:

toImageCoordinates[{x_, y_}] := {
  Rescale[x, {-1, 1}, {0, 200}],
  Rescale[y, {-1, 1}, {0, 200}]
  }

primitives = Join[
   Point@*toImageCoordinates /@ embedding,
   Line@*toImageCoordinates /@ coords
   ];

Cette fonction dessinera n'importe quelle primitive avec une lueur:

draw[primitive_, size_, glow_] := Module[{bg, img},
  bg = ConstantImage[White, 200];
  img = HighlightImage[bg, {
     Black,
     PointSize[Large],
     Thick,
     primitive
     }];
  ColorNegate@Image[Divide[
      ImageData@DistanceTransform[img],
      size Sqrt[2]
      ]^glow]
  ]

draw[First@primitives, 200, 0.2]

Maintenant, le plan est de mapper cette fonction sur toutes les primitives.

images = draw[#, 200, 0.2] & /@ primitives;
ImageAdd @@ images // ImageAdjust

Il est évident à partir de là que les bords et les points peuvent avoir différentes quantités de lueur. En raison des contraintes de temps, je ne ferai pas la fonction qui met tout cela ensemble dans une fonction de «graphe lumineux», mais je laisse cela ici comme une approche possible pour résoudre ce problème.