Светящийся взвешенный граф (сеть): вершины и ребра

Aug 19 2020

Я пытаюсь найти способ (желательно простой и оптимизированный по производительности / скорости для больших графиков) сделать следующее:

  • Стилизация вершин графа светящимся эффектом и его интенсивность в зависимости от VertexWeight

  • Стилизация ребер графа светящимся эффектом и его интенсивность в зависимости от EdgeWeight

  • DirectedEdgeТакже желательна укладка с эффектом свечения (хотя для простоты можно начинать с UndirectedEdge)

Например, примерно так:

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

Я ищу визуал, похожий на этот ниже, за исключением того, что края тоже должны светиться:

Проблемы, с которыми я столкнулся.

1. Простая реализация потрясающего свечения

Я видел различные эффекты свечения (в том числе ЭТО про светящиеся точки), но я не был экспертом по лучшим визуальным идеям и идеям производительности. Удивительно, но я не так уж много видел светящихся линий вокруг. Я бы наивно начал с чего-то вроде этого, но, вероятно, это можно улучшить визуально и с точки зрения производительности:

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. Передача веса в свечение

Хотя я знаю VertexShapeFunctionи EdgeShapeFunction, я не совсем уверен, как оптимально передать им веса ... и являются ли эти свойства правильным подходом.

Свечение встроенных функций

Я заметил, что эти функции дают некоторое свечение:

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

И, как заметил @EC в своем ответе ниже, что-то вроде

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

Спасибо, ваша помощь очень ценится!

Ответы

14 flinty Aug 19 2020 at 22:41

Вы можете получить общий эффект свечения ImageAddс помощью размытой копии маски изображения. По общему признанию, это немного простовато, но эффект впечатляет. Я решил сделать сеть «мозгов» с помощью AnatomyDataи NearestNeighbourGraphсделать его похожим на какой - то более раздутой AI маркетинге вещи:

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

Чтобы веса влияли на размер свечения, вам, вероятно, понадобится использовать EdgeShapeFunctionи VertexShapeFunction. Я создал текстуру рекламного щита с эффектом линзы с альфа-каналом и использовал это изображение для вершин:

Я также использовал эффект краевого свечения, который вы упомянули в вопросе, который складывает линии. Края с большим весом должны иметь большее свечение, а вершины с большим весом будут иметь большее свечение:

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]

Вместо того, чтобы использовать описанный выше трюк с наложением линий и непрозрачностью для создания светящихся краев, вы также можете использовать текстурированные полигоны. Это быстрее, но недостатком является то, что когда края становятся слишком толстыми, крышки видны и некрасивы:

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 дает нам карту расстояний того типа, который нам нужен для свечения.

Сначала мы определяем источник света:

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

Затем мы вычисляем преобразование расстояния. Мы масштабируем его так, чтобы 1 на получившемся изображении соответствовала диагонали изображения.

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

Число 0,2 определяет, насколько быстро гаснет свечение.

Затем мы можем применить цвет к свечению:

glow ConstantImage[Red, 200]

И мы даже можем применять цветовые функции:

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

Создание приятной цветовой функции будет ключом к созданию красивого свечения, как в вашем примере.

Создать светящийся график с помощью этой техники довольно просто. Каждое ребро - это линия, а каждая вершина - это точка или диск. В конце концов, мы можем собрать их в одно изображение.

Я предоставлю читателю возможность создать для этого надежную функцию. Приведу небольшой пример.

Мы будем использовать для примера граф Паппа:

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

Чтобы нарисовать его на изображении, а не на графике, необходимо изменить масштаб координат:

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

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

Эта функция будет рисовать любой примитив со свечением:

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]

Теперь план состоит в том, чтобы отобразить эту функцию на все примитивы.

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

Из этого очевидно, что края и точки могут иметь разное количество свечения. Из-за нехватки времени я не буду создавать функцию, которая объединяет все это в функцию «светящегося графика», но я оставлю это здесь как возможный подход к решению этой проблемы.