//=========================================================================== // A FRAMEWORK FOR SOCIAL NETWORKS // April 2006 // // For the Shape objects, you must write your own event handlers. // They must also be referenced in Unit1.h and in FormCreate. // // DONE: Network configurations may be saved and opened. // Random and hierarchical networks may be created. // Nodes (agents) may be linked by right-click drag-and-drop. // Nodes (agents) may be moved by left-click drag-and-drop. // Node (agent) colors correlate with their tags. // Node (agent) sizes correlate with their strength. // Nodes (agents) can wander with random velocities and directions. // // TO DO: Correlate link width with flow of traffic. // Distinguish and visualize directionality of links. // Compute strength as f(number of links, flow). // Dynamically evolve all parameters. // Consider interrelated networks of different kinds. // Consider spatial proximity in computing interactions. //=========================================================================== #include #include // enables trigonometric functions #pragma hdrstop #include "Unit1.h" #include // for AnsiString functions #include // for AnsiString functions #include "stdlib.h" // enables the randomizer #include "time.h" // enables the randomizer #include // enables streaming file inputs & outputs //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { // Double buffering eliminates some screen flicker... DoubleBuffered = true; } //=========================================================================== // GLOBAL VARIABLES //=========================================================================== // ------------------------------------------------- your program begins here #define POP 50 // POPULATION #define RAD 5 // SMALLEST RADIUS OF CIRCLE SHAPE TShape *shape[POP] = {0}; // sets up an array of shapes TLabel *label[POP] = {0}; // sets up an array of labels class anAgent { // sets up an array of agent information public: float velocity; double direction; double newDirection; double newDistance; double lastDistance; int strength; float x; float y; } agent[POP]; // array of links[from][to] which is directonal int links[POP][POP] = {0}; // used when we want to connect to agents who are not already linked int agentLinked[POP] = {0}; bool stop = true; int iterations; int i; int visible; int chosenVehicle; // enables drag and drop int shapeDownX, shapeDownY; int shapeDownButton; bool agentWasJustChosen = false; //=========================================================================== // FUNCTIONS //=========================================================================== //---------------------------------------------------------------- color ramp int colorRamp(int range, int value) { int pixelDistanceAlongPath = (value * 1792) / range; int red, green, blue; // Which edge of the color cube are we on? if (pixelDistanceAlongPath < 256) { // Edge 1 from BLACK to BLUE red=0; green=0; blue=pixelDistanceAlongPath; } else if (pixelDistanceAlongPath < 512) { // Edge 2 from BLUE to CYAN red =0; green=pixelDistanceAlongPath-256; blue=255; } else if (pixelDistanceAlongPath < 768) { // Edge 3 from CYAN to GREEN red =0; green =255; blue= 255-(pixelDistanceAlongPath-512); } else if (pixelDistanceAlongPath < 1024) { // Edge 4 from GREEN to YELLOW red= (pixelDistanceAlongPath-768); green =255; blue =0; } else if (pixelDistanceAlongPath <1280) { // Edge 5 from YELLOW to RED red =255; green=255-(pixelDistanceAlongPath-1024); blue =0; } else if (pixelDistanceAlongPath < 1536) { // Edge 6 from RED to MAGENTA red =255; green=0; blue=pixelDistanceAlongPath -1280; } else { // Edge 7 from MAGENTA to WHITE red =255; green=pixelDistanceAlongPath-1536; blue =255; } return (RGB(red, green, blue)); } //----------------------------------------------------------------- set links void setLinks (int option) { if (option == 0) { for (int i = 0; i < POP; i++) { agentLinked[i] = 0; for (int j = 0; j < POP; j++) { links[i][j] = 0; } } } else { for (int i = 0; i < POP; i++) { for (int j = 0; j < POP; j++) { if (links[i][j] > 0) { links[i][j] = 1; } } } } } //----------------------------------------------------- random random network void randomNet (void) { // random links are additive int numLinks = 40; int from, to; for (int i = 0; i < numLinks; i++) { from = random(POP); to = random(POP); links[from][to] = 1; // link in both directions links[to][from] = 1; // link in both directions } } //----------------------------------------------- random hierarchical network void hierarchicalNet (void) { // hierarchical links cannot be added to other link structures setLinks(0); int numLinks = 3; int level0, level1, level2, level3, level4, level5; level1 = random(POP); // find an unlinked agent while (agentLinked[level1] == 1) level1 = random(POP); agentLinked[level1] = 1; for (int i = 0; i < numLinks; i++) { level2 = random(POP); // find an unlinked agent while (agentLinked[level2] == 1) level2 = random(POP); links[level1][level2] = 1; // link in both directions links[level2][level1] = 1; // link in both directions agentLinked[level2] = 1; for (int j = 0; j < numLinks; j++) { level3 = random(POP); // find an unlinked agent while (agentLinked[level3] == 1) level3 = random(POP); links[level2][level3] = 1; // link in both directions links[level3][level2] = 1; // link in both directions agentLinked[level3] = 1; for (int k = 0; k < numLinks; k++) { level4 = random(POP); // find an unlinked agent while (agentLinked[level4] == 1) level4 = random(POP); links[level3][level4] = 1; // link in both directions links[level4][level3] = 1; // link in both directions agentLinked[level4] = 1; } } } } //-------------------------------------------------------------- render links void renderLinks (void) { // This renders links twice, once in each direction Form1->Refresh(); for (int i = 0; i < POP; i++) { for (int j = 0; j < POP; j++) { if (links[i][j] >= 1) { Form1->Canvas->Pen->Width = links[i][j]; Form1->Canvas->MoveTo( shape[i]->Left + RAD + agent[i].strength *2, shape[i]->Top + RAD + agent[i].strength *2); Form1->Canvas->LineTo( shape[j]->Left + RAD + agent[j].strength *2, shape[j]->Top + RAD + agent[j].strength *2); } } } } //------------------------------------------------------------------- shuffle void shuffle (void) { for (int i = 0; i < POP; i++) { agent[i].x = random(450); shape[i]->Left = agent[i].x; agent[i].y = random(500); shape[i]->Top = agent[i].y; agent[i].direction = (float(random(1000)) / 500) * M_PI; agent[i].velocity = float(random(1000)) / 1500 + .1; label[i]->Left = agent[i].x; label[i]->Top = agent[i].y - 15; } } //--------------------------------------------------------------------- reset void reset (void) { iterations = 0; Form1->EditIterations->Text = iterations; shuffle(); renderLinks(); } //-------------------------------------------------------------------- circle void circle (void) { for (int i = 0; i < POP; i++) { agent[i].x = 225 + 180 * cos(i * (2 * M_PI / POP)); agent[i].y = 250 + 180 * sin(i * (2 * M_PI / POP)); shape[i]->Left = agent[i].x; shape[i]->Top = agent[i].y; label[i]->Left = agent[i].x; label[i]->Top = agent[i].y - 15; } } //----------------------------------------------------------------- circulate // The code for this function is bad void circulate (void) { for (int j = 0; j < 100; j++) { for (int i = 0; i < POP; i++) { agent[i].newDistance = sqrt( pow(agent[i+1].x - agent[i].x, 2) + pow(agent[i+1].y - agent[i].y, 2)); if (agent[i+1].x - agent[i].x == 0) { agent[i].newDirection = atan(0); } else agent[i].newDirection = atan( agent[i+1].y - agent[i].y / agent[i+1].x - agent[i].x); } agent[23].newDistance = sqrt( pow(agent[POP - 1].x - agent[0].x, 2) + pow(agent[POP - 1].y - agent[0].y, 2)); agent[23].newDirection = atan( agent[0].y - agent[POP - 1].y / agent[0].x - agent[POP - 1].x); agent[i].x += agent[i].newDistance * cos(agent[i].newDirection); agent[i].y += agent[i].newDistance * sin(agent[i].newDirection); shape[i]->Left = agent[i].x; shape[i]->Top = agent[i].y; } } //---------------------------------------------------------------------- step void step (void) { iterations++; Form1->EditIterations->Text = iterations; for (i = 0; i < POP; i++) { //if (!(agentWasJustChosen && (chosenVehicle == i))) { agent[i].x += agent[i].velocity * cos(agent[i].direction) * 10; agent[i].y += agent[i].velocity * sin(agent[i].direction) * 10; shape[i]->Left = agent[i].x; shape[i]->Top = agent[i].y; label[i]->Left = shape[i]->Left; label[i]->Top = shape[i]->Top - 15; if (shape[i]->Left >= 450 || shape[i]->Left <= 0) { agent[i].direction = M_PI - agent[i].direction; } if (shape[i]->Top >= 500 || shape[i]->Top <= 0) { agent[i].direction = 2 * M_PI - agent[i].direction; } if (agent[i].direction > 2 * M_PI) { agent[i].direction = agent[i].direction - 2 * M_PI; } if (agent[i].direction < 0) { agent[i].direction = agent[i].direction + 2 * M_PI; } //} renderLinks(); /* // This group of commands allows the shapes to wrap around... if (shape[i]->Left >= 400) shape[i]->Left = 0; else if (shape[i]->Left <= 0) shape[i]->Left = 400; if (shape[i]->Top >= 400) shape[i]->Top = 0; else if (shape[i]->Top <= 0) shape[i]->Top = 400; */ } } //=========================================================================== // EVENT HANDLERS //=========================================================================== //------------------------------------------------------------ On Form Create void __fastcall TForm1::FormCreate(TObject *Sender) { randomize(); setLinks(0); // defines an array of shapes and labels with properties and events for (int i = 0; i < POP; i++) { shape[i] = new TShape(this); shape[i]->Parent = Form1; shape[i]->Visible = true; shape[i]->OnMouseDown = ShapeMouseDown; shape[i]->OnMouseUp = ShapeMouseUp; shape[i]->OnMouseMove = ShapeMouseMove; shape[i]->Height = (RAD + agent[i].strength *2) * 2; shape[i]->Width = (RAD + agent[i].strength *2) * 2; shape[i]->Shape = stCircle; shape[i]->Tag = i; shape[i]->Pen->Width = 2; shape[i]->Pen->Color = static_cast (colorRamp(POP + 1, POP - shape[i]->Tag)); shape[i]->Brush->Color = static_cast (colorRamp(POP + 1, shape[i]->Tag)); label[i] = new TLabel(this); label[i]->Parent = Form1; label[i]->Visible = false; //label[i]->OnMouseDown = LabelMouseDown; //label[i]->OnMouseUp = LabelMouseUp; //label[i]->OnMouseMove = LabelMouseMove; //label[i]->Height = (RAD + agent[i].strength *2) * 2; //label[i]->Width = (RAD + agent[i].strength *2) * 2; //label[i]->Label = stCircle; label[i]->Caption = i; //label[i]->Pen->Width = 2; //label[i]->Pen->Color = static_cast //(colorRamp(POP + 1, POP - label[i]->Tag)); //label[i]->Brush->Color = static_cast //(colorRamp(POP + 1, label[i]->Tag)); } shuffle(); } //------------------------------------------------------- On Shape Mouse Down // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h and the FormCreate event handler. void __fastcall TForm1::ShapeMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TShape *shape = dynamic_cast(Sender); // Captures the index number of the shape that was clicked chosenVehicle = shape->Tag; // Remembers that a shape was chosen agentWasJustChosen = true; shapeDownButton = Button; shapeDownX = X; shapeDownY = Y; } //--------------------------------------------------------- On Shape Mouse Up // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h and the FormCreate event handler. void __fastcall TForm1::ShapeMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TShape *shape = dynamic_cast(Sender); if (shapeDownButton == 0) { agent[shape->Tag].x = shape->Left; agent[shape->Tag].y = shape->Top; label[shape->Tag]->Left = shape->Left; label[shape->Tag]->Top = shape->Top - 15; agentWasJustChosen = false; renderLinks(); Application->ProcessMessages(); } else { int strength = Form1->TrackBarLinkStrength->Position; links[chosenVehicle][shape->Tag] = strength; // link in both directions links[shape->Tag][chosenVehicle] = strength; // link in both directions renderLinks(); } } //------------------------------------------------------- On Shape Mouse Move // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h and the FormCreate event handler. void __fastcall TForm1::ShapeMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (shapeDownButton == 0) { TShape *shape = dynamic_cast(Sender); if (agentWasJustChosen) { shape->Left = shape->Left + X - shapeDownX; shape->Top = shape->Top + Y - shapeDownY; label[shape->Tag]->Left = shape->Left; label[shape->Tag]->Top = shape->Top - 15; } else { // When the mouse moves over an shape // its index number is displayed EditVehicle->Text = shape->Tag; EditAgentStrength->Text = agent[shape->Tag].strength; Form1->TrackBarAgentStrength->Position = agent[shape->Tag].strength; EditDirection->Text = agent[shape->Tag].direction; EditVelocity->Text = agent[shape->Tag].velocity; EditCaption->Text = label[shape->Tag]->Caption; } } } //-------------------------------------------------------- On Form Mouse Down void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { // not necessary in the current version /*if (agentWasJustChosen) { // moves the shape to the position that was just clicked agent[chosenVehicle].x = X; agent[chosenVehicle].y = Y; shape[chosenVehicle]->Left = X - shapeDownX; shape[chosenVehicle]->Top = Y - shapeDownY; agentWasJustChosen = false; } */ } //---------------------------------------------------------------- Run Button void __fastcall TForm1::ButtonRunClick(TObject *Sender) { stop = false; while (stop == false) { step(); Application->ProcessMessages(); } } //--------------------------------------------------------------- Step Button void __fastcall TForm1::ButtonStepClick(TObject *Sender) { stop = true; step(); } //--------------------------------------------------------------- Stop Button void __fastcall TForm1::ButtonStopClick(TObject *Sender) { stop = true; } //-------------------------------------------------------------- Reset Button void __fastcall TForm1::ButtonResetClick(TObject *Sender) { reset(); } //------------------------------------------------------- Clear Screen Button void __fastcall TForm1::ButtonClearScreenClick(TObject *Sender) { Form1->Refresh(); renderLinks(); } //------------------------------------------------------- Render Links Button void __fastcall TForm1::ButtonRenderLinksClick(TObject *Sender) { renderLinks(); } //---------------------------------------------------------- Circulate Button void __fastcall TForm1::ButtonCirculateClick(TObject *Sender) { //circulate(); // the code for this function is bad } //----------------------------------------------------- Random Network Button void __fastcall TForm1::ButtonRandomNetworkClick(TObject *Sender) { randomNet(); renderLinks(); } //----------------------------------------------- Hierarchical Network Button void __fastcall TForm1::ButtonHierarchicalClick(TObject *Sender) { hierarchicalNet(); renderLinks(); } //------------------------------------------------------ Destroy Links Button void __fastcall TForm1::ButtonDestroyLinksClick(TObject *Sender) { setLinks(0); renderLinks(); } //------------------------------------------------------------- Circle Button void __fastcall TForm1::ButtonCircleClick(TObject *Sender) { circle(); renderLinks(); } //------------------------------------------------------------ Shuffle Button void __fastcall TForm1::ButtonRandomClick(TObject *Sender) { shuffle(); renderLinks(); } //---------------------------------------------------------------- Form Paint void __fastcall TForm1::FormPaint(TObject *Sender) { //renderLinks(); this collides with Refresh(); } //--------------------------------------------------------------- Save Button void __fastcall TForm1::ButtonSaveClick(TObject *Sender) { if (Form1->SaveDialog1->Execute()) { ofstream outfile(Form1->SaveDialog1->FileName.c_str()); int left, top; for (int i = 0; i < POP; i++) { outfile << agentLinked[i] << " "; for (int j = 0; j < POP; j++) { outfile << links[i][j] << " "; // array of links [from][to] } outfile << agent[i].velocity << " "; outfile << agent[i].direction << " "; outfile << agent[i].newDirection << " "; outfile << agent[i].newDistance << " "; outfile << agent[i].lastDistance << " "; outfile << agent[i].strength << " "; outfile << agent[i].x << " "; outfile << agent[i].y << " "; outfile << endl; left = shape[i]->Left; outfile << left << " "; top = shape[i]->Top; outfile << top << " "; outfile << endl; } for (int i = 0; i < POP; i++) { if (label[i]->Caption == "") outfile << "$ "; else outfile << label[i]->Caption.c_str() << " "; outfile << label[i]->Visible << " "; } outfile.close(); renderLinks(); } } //--------------------------------------------------------------- Open Button void __fastcall TForm1::ButtonOpenClick(TObject *Sender) { if (Form1->OpenDialog1->Execute()) { ifstream infile(Form1->OpenDialog1->FileName.c_str()); int left, top; for (int i = 0; i < POP; i++) { infile >> agentLinked[i]; for (int j = 0; j < POP; j++) { infile >> links[i][j]; // array of links [from][to] } infile >> agent[i].velocity; infile >> agent[i].direction; infile >> agent[i].newDirection; infile >> agent[i].newDistance; infile >> agent[i].lastDistance; infile >> agent[i].strength; shape[i]->Height = (RAD + agent[i].strength *2) * 2; shape[i]->Width = (RAD + agent[i].strength *2) * 2; infile >> agent[i].x; infile >> agent[i].y; infile >> left; shape[i]->Left = left; infile >> top; shape[i]->Top = top; } for (int i = 0; i < POP; i++) { char* cap = new char[100]; infile >> cap; if (cap[0] == '$') label[i]->Caption = ""; else label[i]->Caption = cap; infile >> visible; if (visible == 0) label[i]->Visible = false; else label[i]->Visible = true; label[i]->Left = agent[i].x; label[i]->Top = agent[i].y - 15; } infile.close(); renderLinks(); } } //------------------------------------------------------ Enter Changes Button void __fastcall TForm1::ButtonEnterChangesClick(TObject *Sender) { int who; who = StrToInt(Form1->EditVehicle->Text); if (who >= 0 && who < POP) { int imp = StrToInt(EditAgentStrength->Text); if (imp < 10) { agent[who].strength = imp; agent[who].direction = StrToFloat(EditDirection->Text); agent[who].velocity = StrToFloat(EditVelocity->Text); shape[who]->Height = (RAD + agent[who].strength *2) * 2; shape[who]->Width = (RAD + agent[who].strength *2) * 2; label[who]->Caption = Form1->EditCaption->Text; if (Form1->RadioGroupLabelVisible->ItemIndex == 0) label[who]->Visible = false; else label[who]->Visible = true; renderLinks(); } } } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarAgentStrengthChange(TObject *Sender) { int who; who = StrToInt(Form1->EditVehicle->Text); if (who >= 0 && who < POP) { agent[who].strength = TrackBarAgentStrength->Position; Form1->EditAgentStrength->Text = agent[who].strength; agent[who].direction = StrToFloat(EditDirection->Text); agent[who].velocity = StrToFloat(EditVelocity->Text); shape[who]->Height = (RAD + agent[who].strength *2) * 2; shape[who]->Width = (RAD + agent[who].strength *2) * 2; renderLinks(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonLevelClick(TObject *Sender) { // returns all agent's strength variable to zero (0) for (int i = 0; i < POP; i++) { agent[i].strength = 0; shape[i]->Height = (RAD + agent[i].strength *2) * 2; shape[i]->Width = (RAD + agent[i].strength *2) * 2; renderLinks(); } } //---------------------------------------------------------- That's All Folks void __fastcall TForm1::ButtonLevelLinksClick(TObject *Sender) { setLinks(1); renderLinks(); } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarLinkStrengthChange(TObject *Sender) { Form1->EditLinkStrength->Text = TrackBarLinkStrength->Position; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditCaptionChange(TObject *Sender) { Form1->RadioGroupLabelVisible->ItemIndex = 1; } //--------------------------------------------------------------------------- void __fastcall TForm1::RadioGroupLinkDirectionalClick(TObject *Sender) { RadioGroupLinkDirectional->ItemIndex = 0; } //---------------------------------------------------------------------------