Chord diagram

Hello,

I was wondering whether ROOT has the option for plotting chord diagrams, see: Help Online - Origin Help - Chord Diagram/Chord Diagram with Ratio Layout
circularGraph - File Exchange - MATLAB Central

If not, maybe it can be implemented in the future as a class derived from TGraphPolar using TPolyLines?

Thanks for the attention.

ROOT does not have such diagram. It would required a new implementation. It is circular but I am not sure it should derive from TGraphPolar.

I do not think that TGraphPolar is good starting point - it always drawing polar coordinates.
Chord diagram and circular graph is absolutely new kind of draw options, which could be implemented for the TH2 class. I could try to implement this in JSROOT.

2 Likes

What would be the format of data ? what kind of ROOT data do you want to show ?
As said here:

A chord diagram represents flows or connections between several entities (called nodes ).

I am not sure which kind of ROOT data will map this.

May be a TH2 with the same alpha numeric labels along X and Y ? each label would be “a node” and the content of each bin gives the strength of the connection between two nodes and therefore the thickness of the line connecting them ?
So that will be a special case of TH2

In MATLAB, the data is expressed as a 2D matrix.

The strength of connection between nodes i and j are just matrix[i][j]. The diagonal matrix[i][i] is zero. The matrix should be symmetric matrix[i][j] = matrix[j][i]. (Or it should be specified if it’s the lower or the upper triangular matrix that will be considered for plotting)

I agree, a TH2 could work well, but it would be good if it would also support passing a raw C array (2D matrix).

Also, it would be great if the transparency and color of the connecting line, not just the thickness, can be set as function of strength.

Here is a “quick and dirty” macro producing a circular graph. It is really just the basic structure … nothing you can use … just the idea:

TCanvas *c1;

void chord(TH2F *h)
{
   c1->cd(2)->Range(-1, -1, 1, 1);;
   TEllipse *circle = new TEllipse(0., 0., 0.8);
   circle->SetLineWidth(1);

   circle->Draw("L");
   TAxis *xaxis;
   xaxis=h->GetXaxis();
   THashList *labels = xaxis->GetLabels();
   int ndiv = xaxis->GetLast()-xaxis->GetFirst()+1;
   float da = 2*TMath::Pi()/ndiv;

   float ax = 0 ;
   float ay = 0;
   for (int i=1; i<=ndiv; i++) {
      for (int j=1; j<=ndiv; j++) {
         if (h->GetBinContent(i,j)>0){
            auto l = new TLine(0.8*TMath::Cos(ax), 0.8*TMath::Sin(ax),
                               0.8*TMath::Cos(ay), 0.8*TMath::Sin(ay));
            l->SetLineWidth(h->GetBinContent(i,j));
            l->SetLineColor(h->GetBinContent(i,j));
            l->Draw();
         }
         ay = ay+da;
      }
      ay = 0;
      ax = ax+da;
   }

   TIter next(labels);
   auto t= new TText();
   t->SetTextAlign(22);
   t->SetTextSize(0.03);
   auto  m = new TMarker();
   m->SetMarkerStyle(20);

   float a = 0;
   while (TObject *obj = next()) {
      t->DrawText(0.85*TMath::Cos(a), 0.85*TMath::Sin(a), obj->GetName());
      m->DrawMarker(0.8*TMath::Cos(a), 0.8*TMath::Sin(a));
      a = a+da;
   }
}

void chord_diagrams()
{
   const Int_t n = 12;
   const char *L[n]  = {"A","B","C","D","E","F","G","H","I","J","K","L"};

   c1 = new TCanvas("c","c",10,10,1200,600);
   c1->Divide(2,1);

   auto *h = new TH2F("h","",n,0,1,n,0,1);
   h->SetStats(0);

   for (int i=0;i<n;i++) h->Fill(L[i],L[i],0);

   h->Fill(L[2],L[3],1);
   h->Fill(L[3],L[2],1);
   h->Fill(L[5],L[3],2);
   h->Fill(L[3],L[5],2);
   h->Fill(L[9],L[4],5);
   h->Fill(L[4],L[9],5);

   c1->cd(1)->SetGrid();
   h->Draw("text");
   chord(h);
}

Cool, thanks

I guess that the second for loop should stop at j<i ? (To not draw twice the same, as it’s symmetrical?)

As I said it is just a quick and dirty prototype. Fell free to improve it. The initialization and filling of the histogram can be improved too as the histogram used has some strict characteristics.

Hi,

I slightly modify Olivier macro:

void chord_diagrams()
{
   const Int_t n = 12;
   const char *L[n]  = {"A","B","C","D","E","F","G","H","I","J","K","L"};

   c1 = new TCanvas("c","c",10,10,1200,600);
   c1->Divide(2,1);

   auto *h = new TH2F("h","",n,0,1,n,0,1);
   h->SetStats(0);

   for (int i=0;i<n;i++) h->Fill(L[i],L[i],0);

   h->Fill(L[2],L[3],1);
   h->Fill(L[3],L[2],1);
   h->Fill(L[5],L[3],2);
   h->Fill(L[3],L[5],2);
   h->Fill(L[9],L[4],5);
   h->Fill(L[4],L[9],5);

   c1->cd(1)->SetGrid();
   h->Draw("text");
   c1->cd(2);
   h->Draw("circular");
}

This is produced canvas:

To get it working, with current root master do:

[shell] export JSROOTSYS=https://jsroot.gsi.de/dev/
[shell] root --web chord_diagrams.C

Later I will commit modified JSROOT code into ROOT repository.
After that ROOT will work directly - without need to set JSROOTSYS variable.

I also want to implement chord diagram - it should be also relatively simple.

Regards,
Sergey

3 Likes

Looks nice ! The next step might be to make some special methods to book and fill “chord histograms” as they are really special:

  • Same label along X and Y axis
  • Diagonal = 0
  • Symmetrical.

Hi @ferhue,

I now implement “chord” drawing option, taking already provided functionality
of d3.js - see Chord Diagram / D3 / Observable

If in the macro “chord” instead of “circle” option is specified, output will be:

Regards,
Sergey

2 Likes

So beautiful!
Thanks so much for the prompt implementation!!