13
$\begingroup$

I am trying to find a way (desirably simple and performance/speed optimized for larger graphs) to do the following :

  • Styling graph vertexes by glow-effect and its intensity depending on VertexWeight

  • Styling graph edges by glow-effect and its intensity depending on EdgeWeight

  • DirectedEdge glow-effect styling is desirable too (while for simplicity things can start at UndirectedEdge)

For example for something like this:

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

i am looking for a visual similar to this one below, except that edges need to glow too:

enter image description here

The issues that I am experiencing.

1. Simple implementation of a stunning glow

I've seen various glow effects (including THIS about glowing points) but not an expert on best visual vs performance ideas. Surprisingly also I have not seen much about glowing lines around. I'd naively start with something like this, but that's probably can be improved visually and performance-wise:

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]

enter image description here

2. Passing weights to glow

While I am aware of VertexShapeFunction and EdgeShapeFunction, I am not quite sure how to optimally pass the weights to them... and if these properties are the right approach.

Glow in built-in functions

I've noticed that these functions produces some glow:

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

enter image description here

And as noticed by @E.C. in his answer below something like

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

enter image description here

Thank you, your help is highly appreciated!

$\endgroup$
13
$\begingroup$

You can get an overall glow effect by an ImageAdd with a blurred copy of the image mask. Admittedly it's a bit basic, but the effect is compelling. I chose to make a 'brain' network using AnatomyData and NearestNeighbourGraph to make it look like some over-hyped AI marketing thing:

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]]]]

img

To get the weights to affect the size of the glow you'll probably need to use the EdgeShapeFunction and VertexShapeFunction. I created a billboard texture of a lens effect with alpha and I used this image for the vertices:

img

I also used the edge glow effect you mentioned in the question which stacks the lines. Edges with more weight should have more glow, and vertices with more weight will have a larger flare:

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]

img

Rather than use the line stacking / opacity trick above to produce the glowing edges, you could also use textured polygons instead. This is faster but a disadvantage is when the edges become too thick the caps are visible and ugly:

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]

glow 2

| improve this answer | |
$\endgroup$
  • $\begingroup$ +1 Very beautiful! Do you think edges would benefit from the same approach you are using for vertices? ...some thin glow mask $\endgroup$ – Vitaliy Kaurov Aug 19 at 19:16
  • $\begingroup$ @VitaliyKaurov possibly - if I can find out how to texture a line with a perpendicular fade-to-transparency gradient. Unfortunately though Line does not want to accept VertexTextureCoordinates, so maybe one could use a thin oriented cuboid, polygon, or parallelogram for the edges instead which would allow texturing. $\endgroup$ – flinty Aug 19 at 20:15
  • $\begingroup$ @VitaliyKaurov updated - it turns out it wasn't too hard to do with polygon after all. $\endgroup$ – flinty Aug 19 at 20:38
  • $\begingroup$ Wow, impressive, thank you ! $\endgroup$ – Vitaliy Kaurov Aug 19 at 21:26
  • $\begingroup$ This seems to be some picture from the Mathematica documentation for GradientFilter section properties and relations with the built-in RemoveBackground applied. Maybe there is some noise added. It is almost monochrome with this turquoise color. This may lead to a personal concept, how to make the glow and adapt the colors. $\endgroup$ – Steffen Jaeschke Aug 30 at 15:13
8
$\begingroup$

DistanceTransform gives us a distance map of the type that we need for glow.

First we define the light source:

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

Mathematica graphics

Next, we compute the distance transform. We scale it such that 1 in the resulting image corresponds to the diagonal of the image.

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

Mathematica graphics

The number 0.2 controls how quickly the glow dies off.

Next, we can apply a color to the glow:

glow ConstantImage[Red, 200]

Mathematica graphics

And we can even apply color functions:

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

Mathematica graphics

Creating a nice color function will be key to create a nice glow like the one in your example.

Creating a glowing graph is quite straight-forward using this technique. Every edge is a line and every vertex is a point or a disk. In the end, we can put them together into one image.

I'll leave it to the reader to create a robust function for this. I will just make a small example.

We'll use the Pappus graph for the example:

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

Mathematica graphics

Drawing it onto an image instead of in a graphics requires rescaling the coordinates:

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

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

This function will draw any primitive with a glow:

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]

Mathematica graphics

Now the plan is to map this function over all primitives.

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

Mathematica graphics

It is obvious from this that edges and points can have different amounts of glow. Because of time constraints, I will not make the function that puts all this together into a "glowing graph" function, but I leave this here as a possible approach to solving this problem.

| improve this answer | |
$\endgroup$
  • $\begingroup$ This is very beautiful, and a neat approach, but here glow intensity does not the depend on VertexWeight and EdgeWeight for a particular vertex or edge right? Those are essential requirements. $\endgroup$ – Vitaliy Kaurov Aug 19 at 7:55
  • 1
    $\begingroup$ @VitaliyKaurov I didn’t have time to make more examples, but this approach is very flexible. As you can see, we create one mask for each primitive, i.e. each edge and each vertex. Each of those masks can have their own values for how much they should glow. $\endgroup$ – C. E. Aug 19 at 8:03
  • $\begingroup$ Sounds like an interesting approach +1 :-) Thank you for your efforts! $\endgroup$ – Vitaliy Kaurov Aug 19 at 13:40
  • $\begingroup$ @VitaliyKaurov Due to time constraints, I am not going to be able to build an automated solution for the problem, but I fleshed out my description of how it can be done with DistanceTransform a bit. $\endgroup$ – C. E. Aug 20 at 20:19
  • 1
    $\begingroup$ thank you very much - what you did already helps a lot. $\endgroup$ – Vitaliy Kaurov Aug 20 at 20:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.