Gráfico ponderado brillante (red): vértices y bordes

Aug 19 2020

Estoy tratando de encontrar una manera (deseablemente simple y rendimiento / velocidad optimizados para gráficos más grandes) para hacer lo siguiente:

  • Aplicar estilo a los vértices del gráfico por efecto de brillo y su intensidad en función de VertexWeight

  • Estilo de los bordes del gráfico por efecto de brillo y su intensidad dependiendo de EdgeWeight

  • DirectedEdgeEl estilo con efecto de brillo también es deseable (mientras que, por simplicidad, las cosas pueden comenzar en UndirectedEdge)

Por ejemplo, para algo como esto:

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

Estoy buscando una imagen similar a esta a continuación, excepto que los bordes también deben brillar:

Los problemas que estoy experimentando.

1. Implementación simple de un brillo impresionante

He visto varios efectos de brillo (incluido ESTO sobre puntos brillantes) pero no soy un experto en las mejores ideas visuales frente a las de rendimiento. Sorprendentemente, tampoco he visto mucho sobre líneas brillantes alrededor. Ingenuamente comenzaría con algo como esto, pero eso probablemente se pueda mejorar visualmente y en términos de rendimiento:

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. Pasar pesas para brillar

Si bien sé VertexShapeFunctiony EdgeShapeFunctionno estoy muy seguro de cómo pasarles los pesos de manera óptima ... y si estas propiedades son el enfoque correcto.

Resplandor en funciones integradas

He notado que estas funciones producen algo de brillo:

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

Y como lo notó @EC en su respuesta a continuación, algo como

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

¡Gracias, su ayuda es muy apreciada!

Respuestas

14 flinty Aug 19 2020 at 22:41

Puede obtener un efecto de brillo general ImageAddcon una copia borrosa de la máscara de imagen. Es cierto que es un poco básico, pero el efecto es convincente. Elegí hacer una red de 'cerebros' usando AnatomyDatay NearestNeighbourGraphhacer que pareciera una cosa de marketing de IA exagerada:

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

Para que los pesos afecten el tamaño del brillo, probablemente necesitará usar EdgeShapeFunctiony VertexShapeFunction. Creé una textura de cartelera de un efecto de lente con alfa y usé esta imagen para los vértices:

También utilicé el efecto de brillo de borde que mencionaste en la pregunta que apila las líneas. Los bordes con más peso deberían tener más brillo y los vértices con más peso tendrán un destello más grande:

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]

En lugar de usar el truco de apilamiento / opacidad de líneas anterior para producir los bordes brillantes, también puede usar polígonos texturizados. Esto es más rápido, pero una desventaja es que cuando los bordes se vuelven demasiado gruesos, las tapas son visibles y feas:

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 nos da un mapa de distancias del tipo que necesitamos para iluminar.

Primero definimos la fuente de luz:

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

A continuación, calculamos la transformación de distancia. Lo escalamos de manera que 1 en la imagen resultante corresponda a la diagonal de la imagen.

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

El número 0.2 controla la rapidez con la que se apaga el brillo.

A continuación, podemos aplicar un color al resplandor:

glow ConstantImage[Red, 200]

E incluso podemos aplicar funciones de color:

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

Crear una función de color agradable será clave para crear un brillo agradable como el de su ejemplo.

Crear un gráfico brillante es bastante sencillo con esta técnica. Cada borde es una línea y cada vértice es un punto o un disco. Al final, podemos juntarlos en una imagen.

Dejaré que el lector cree una función robusta para esto. Solo daré un pequeño ejemplo.

Usaremos el gráfico de Pappus para el ejemplo:

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

Dibujarlo en una imagen en lugar de en un gráfico requiere cambiar la escala de las coordenadas:

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

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

Esta función dibujará cualquier primitiva con un brillo:

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]

Ahora el plan es mapear esta función sobre todas las primitivas.

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

A partir de esto, es obvio que los bordes y los puntos pueden tener diferentes cantidades de brillo. Debido a las limitaciones de tiempo, no haré la función que pone todo esto junto en una función de "gráfico brillante", pero dejo esto aquí como un posible enfoque para resolver este problema.