My project "sociogram" is web-based, coded in JavaScript. In input of an array containing your social data, it returns a canvas representating your personal sociogram. For example, here's mine:

There is to add future functions, as for instance, enlarging the data of one of the contacts:

That's my code for computation:
      
var width = window.innerWidth;
var height = window.innerHeight;  

var canvas = document.querySelector("canvas");
canvas.width = width;
canvas.height = height;
var c = canvas.getContext("2d");

var tooltip = document.getElementById("tooltip");
var tipc = tooltip.getContext("2d");

//vars
var profiles = [];
var hit = false;

function drawArc(context, x, y, size, imgsrc = "") {
  if (imgsrc == "") {   
    //Arc
    context.beginPath();
    context.arc(x, y, size, 0, Math.PI * 2, false);
    context.closePath();
    context.strokeStyle = "rgba(0, 0, 0, 1)";
    //context.stroke();
    context.fillStyle = "rgba(200, 50, 50, 1)";
    context.fill(); 
    
    imgsrc = "./image.png";
  }
  
  //Image on arc
  var image = new Image();       
  image.onload = function(){
    context.drawImage(image, x-size, y-size, size*2, size*2);
  }
  image.src = imgsrc; 
}

function drawLine(context, p, id_from, id_to) {
  for (j = 0; j < id_to.length; j++) {
    fX = 0; fY = 0; tX = 0; tY = 0;
    for (k = 0; k < p.length; k++) {
      if (p[k][0] == id_from) {
        fX = p[k][2];
        fY = p[k][3];
      }
      if (p[k][0] == id_to[j]) {
        tX = p[k][2];
        tY = p[k][3];
      }
    }
    if (fX + fY > 0 && tX + tY > 0) {   
    context.beginPath();
    context.moveTo(fX, fY);
    context.lineTo(tX, tY);
    //ToDo: Nicht am Mittelpunkt festmachen - besser machen
    if (fX == width/2 && fY == height/2 || tX == width/2 && tY == height/2) {
      var color = "rgba(200, 50, 50, 1)";
    } else {
      var color = "#b5b5b5";
    }
    context.strokeStyle = color;
    context.stroke();    
    } 
  }
}

function drawText(context, text, x, y) {
  context.font = "12px Arial";
  context.textAlign = "center";
  context.fillStyle = "rgba(200, 50, 50, 1)";
  context.fillText(text, x, y);
}

function getProfileInfo(id) {
  result_b = false;
  for (i = 0; i <= p1.length - 1; i++) {
    if (id == p1[i][0]) {
      result_b = p1[i];
      break;
    }
  }
  return result_b;
}

function setProfile(p) {
  result = 0;
  for (j = 0; j <= p.length - 1; j++) {
    check = true;
    for (k = 0; k <= profiles.length - 1; k++) {
      if (p[j] == profiles[k][0]) {
        check = false;
        break;
      } 
    }
    
    if (check) {
      push = getProfileInfo(p[j]); 
      if (push != false) { 
        profiles.push(push);
        result++;    
        length = profiles.length - 1;
        size = 20;
        for (l = 0; l < profiles[length][1].length; l++) {
          if (profiles[length][1][l] == me[0]) {
            size = 30;
          }
        }              
        profiles[length][4] = size;
      }
    }
  }
  return result;
}

function fetchProfilePos(p, x, y, dival) {
  slice = p.length - 1;
  for (i = 1; i < p.length; i++) {
    distance = false;
    k = 0;
    angle = (i * 360 / slice) * (Math.PI / 180);
    while (!distance) {
      p[i][2] = Math.sin(angle) * (p[i][5]/100) * (dival);
      p[i][2] += x;
      p[i][3] = Math.cos(angle) * (p[i][5]/100) * (dival);
      p[i][3] += y;
      if (Math.sqrt(Math.pow(p[i][2] - p[i-1][2], 2) + Math.pow(p[i][3] - p[i-1][3], 2)) <= p[i][4] * 3) {
        k += 0.01;
        angle = ((i + k) * 360 / slice) * (Math.PI / 180);
      } else {
        distance = true;
      }
      if (p[i][2] + p[i][4] <= 0)       {p[i][2] += -1 * p[i][2] + p[i][4]          }
      if (p[i][2] + p[i][4] >= width)   {p[i][2] -= p[i][2] - width + p[i][4]       }
      if (p[i][3] + p[i][4] <= 0)       {p[i][3] += -1 * p[i][3] + p[i][4]          }
      if (p[i][3] + p[i][4] >= height)  {p[i][3] -= p[i][3] - height + p[i][4] + 24 }
    }
  }  
}

function clipImages(context) {
  context.beginPath();
  for (i = 0; i < profiles.length; i++) {
    context.arc(profiles[i][2], profiles[i][3], profiles[i][4], 0, Math.PI * 2, false);
    context.arc(0, 0, 0, 0, Math.PI * 2, false);
  }
  context.clip(); 
}

function drawProfiles(p, dival = ((width/2 > height/2) ? height : width) / 2) {
  added = setProfile(p[1]);
  fetchProfilePos(profiles.slice(profiles.length - added - 1), p[2],  p[3], dival);
  for (i = profiles.length - added - 1; i < profiles.length; i++) {            
    drawLine(c, profiles, profiles[i][0], profiles[i][1]); 
  }
  for (i = profiles.length - added - 1; i < profiles.length; i++) {            
    //drawLine(c, profiles, profiles[i][0], profiles[i][1]);                                 
    drawArc(c, profiles[i][2], profiles[i][3], profiles[i][4], profiles[i][7]);            
    drawText(c, profiles[i][6], profiles[i][2], profiles[i][3] + profiles[i][4] + 12);
  }
  c.save();
  //clipImages(c);
} 

function handleMouseMove(e) {
  mX = parseInt(e.clientX - canvas.offsetLeft);
  mY = parseInt(e.clientY - canvas.offsetTop);
  
  for (i = 0; i < profiles.length; i++) {
    dX = mX - profiles[i][2];
    dY = mY - profiles[i][3];
    if (Math.sqrt(dX * dX + dY * dY) <= profiles[i][4]) {
      //Sichtbarkeit
        tooltip.style.visibility = "visible";
      //Position
        if (e.clientX + tooltip.width >= window.innerWidth) {
          tooltip.style.left = window.innerWidth - tooltip.width + "px";
        } else {
          tooltip.style.left = mX + 10 + "px";
        }
        if (e.clientY + tooltip.height >= window.innerHeight) {
          tooltip.style.top = window.innerHeight - tooltip.height + "px";
        } else {
          tooltip.style.top = mY + 10 + "px";
        }
      if (!hit) {
        //Const - X & Y
          //Text
            tX = 5;
            tY = 15;
            tipc_text_font = "12px Arial";
            tipc_text_fillstyle = "rgba(200, 50, 50, 1)";
          //Bild / Arc
            bX = 55;
            bY = 75;
            bR = 50;
          //Buttons
            buX1 = 110;
            buY1 = 100;
            buX2 = 185;
            buY2 = 45;
            tipc_button_text = "";
            tipc_button_fillstyle = "rgba(0, 153, 0, 1)";
        //Reset
          tipc.clearRect(0, 0, tooltip.width, tooltip.height);        
        //Inhalt - Text
          tipc.font = tipc_text_font;
          tipc.fillStyle = tipc_text_fillstyle;
          tipc.fillText(profiles[i][6], tX, tY);  
          tipc.fill();       
        //Inhalt - Clipping
          tipc.beginPath();
          tipc.arc(bX, bY, bR, 0, Math.PI * 2);
          tipc.rect(tX, 0, tooltip.width, tY + 5); 
          tipc.rect(buX1, buY1, buX2, buY2);      
          tipc.clip();                                        
        //Inhalt - Bild
          drawArc(tipc, bX, bY, bR, profiles[i][7]);   
        //Inhalt - Beschriebung
        //Inhalt - Buttons
          tipc.beginPath();
          tipc.rect(buX1, buY1, buX2, buY2);
          tipc.fillStyle = tipc_button_fillstyle;
          tipc.fill();       
        hit = true;
      }                     
      break;
    } else {
      hit = false;
    } 
  }
  if (!hit) {
    tooltip.style.visibility = "hidden";  
    tooltip.style.top = "-100%";
  }
}

//profile [id, to, x, y, size, fval, name, image]
var me = [0, [1, 2, 3, 4, 6, 7, 8], width / 2, height / 2, 50, 0, "Me", "./image.jpg"];
var p1 = [
  [1, [0, 2, 3, 4, 5, 6, 7, 8],    0, 0, 0, 30,  "X1", ""],
  [2, [0, 1, 3, 4, 5, 6, 7],       0, 0, 0, 80,  "X2", ""],
  [4, [0, 1, 2, 3, 5, 6, 7],       0, 0, 0, 50,  "X3", ""],
  [5, [1, 2, 3, 7],                0, 0, 0, 100, "X4", ""],
  [3, [0, 1, 2, 4, 5, 6, 7],       0, 0, 0, 30,  "X5", ""],
  [6, [0, 1, 2, 3, 4, 7],          0, 0, 0, 40,  "X6", ""],
  [7, [0, 1, 2, 3, 4, 5, 6, 8, 9], 0, 0, 0, 40,  "X7", ""],
  [9, [7, 9],                      0, 0, 0, 70,  "X8", ""],
  [8, [0, 1, 7],                   0, 0, 0, 40,  "X9", ""]
];

profiles.push(me);
drawProfiles(me);
canvas.addEventListener('mousemove', handleMouseMove);

console.log("--- test ---");
tmp_profiles_length = profiles.length;
for (m = 1; m <= tmp_profiles_length - 1; m++) {
  drawProfiles(profiles[m], ((width/2 > height/2) ? height : width) * 0.125);
}      
clipImages(c);