Parliament chart sample

It is a graph used to improve various visualizations, such as distributions of parliamentarians, assembly members or congressmen. I have not found this graph natively in plotly, but I am adding a simple example of how to generate it from a simple dataset. Parameters are required to generate this chart such as the number of rows, the radius of the initial row, the total angle and the size of the mark. I hope it is useful to you!.

Some functions necessary to generate the radii and angles:

import math

def Sort_Tuple(tup):
    return sorted(tup, key=lambda x: x[1])

def parlamentary_Coord(df,angle_total=180,rows=4, ratio=6,initial='NAME'):    
    arco_total = 0
    angles = []
    for i in range(rows):
        arco_total += math.pi * int(ratio+i)
    for i in range(rows):
        arco_radio = math.pi * int(ratio+i)
        angles.append(angle_total/round(arco_radio/(arco_total/len(df)),0))
    coord = []
    for a in range(len(angles)):
        current_angle = angles[a]/2
        for i in range(int(round(angle_total/angles[a],0))):        
            coord.append((ratio+a, current_angle))
            current_angle += angles[a]
    coord = Sort_Tuple(coord)    
    df["radio"] = list(zip(*coord))[0]
    df["tetha"] = list(zip(*coord))[1]
    df["INITIAL"] = df[initial].apply(lambda x: x[0])
    return df

For this chart we use px.scatter_polar:

import plotly.express as px

#config for parlamentary plot
graph_style = "plotly_dark" #template plotly
height = 500 # height plot
angle_total = 240 # all cover plot, recomended 90Β° - 300Β°
size_marker = 32 #config by size angle_total in design

#df your dataframe
df = parlamentary_Coord(df,angle_total,5,6)

angle_start = (180 - angle_total)/2
angle_end = 180 + (angle_total - 180)/2

# recomended use 'PARTIDO', 'COLOR' in your dataframe 
d_ict = df[["PARTIDO", "COLOR"]]
d_ict = d_ict.drop_duplicates()
dct = dict(d_ict.values)
fig = px.scatter_polar(
    df,
    r="radio",
    theta="tetha",
    color="PARTIDO",
    color_discrete_map=dct,
    text="INITIAL",
    start_angle=angle_start,
    custom_data=["NAME"],
    range_theta=[angle_start, angle_end],
    direction="counterclockwise",
)
fig.update_layout(
    xaxis=dict(showgrid=False),
    yaxis=dict(showgrid=False),
    margin=dict(b=20, r=5, l=5, t=10),
    height=height,
    polar=dict(
        radialaxis=dict(
            showticklabels=False, ticks="", linecolor="rgba(255, 255, 255, 0)"
        ),
        angularaxis=dict(showticklabels=False, ticks="", linecolor="rgba(0,0,0,0)"),
    ),
    polar_radialaxis_gridcolor="rgba(0,0,0,0)",
    polar_angularaxis_gridcolor="rgba(0,0,0,0)",
)
fig.update_traces(textposition="middle center")
fig.update_traces(
    hovertemplate="<b>%{customdata[0]}</b><extra></extra>",
    textfont_size=12,
)
fig.update_layout(
    template=graph_style,
    autosize=True,
    showlegend=False,
    uniformtext_minsize=8,
    uniformtext_mode="hide",
    font=dict(family="Arial, monospace", size=12),
)
fig.update_traces(marker=dict(opacity=0.7, size=size_marker))
fig.show()

Example: angle_total = 240 ; size_marker = 30

Example: angle_total = 180 ; size_marker = 32

Example: angle_total = 90 ; size_marker = 20

6 Likes

Example with dataset Create a Parliament chart with plotly python | by U-Danny | Sep, 2024 | Medium

Hi @U-Danny
:wave: Welcome to the community

Thank you for sharing the code for this beautiful graph. What does ratio=6 stand for in the parlamentary_Coord function?

Hi, the graph is a composition of circles (rows n) ​​ and ratio=6 (relation for better visualization) is the radius of the circle 0 (row 1) radius recommended [4.6] for better visualization. But it can always be improved with some better additional parameter that allows generalizing a better quality graph.

1 Like

@U-Danny Multiplying by math.pi, within your function body, is useless because you perform a division in round, and both the numerator and the denominator contain math.pi as a factor.
Example:
If angle_total=180, rows=4, ratio=6, len(df)=5 then the following lines of code

arco_total = 0
for i in range(rows):
    arco_total +=  math.pi*int(ratio+i)

perform in fact:
arco_total=6Ο€+7Ο€+8Ο€+9Ο€=30Ο€

then round(arco_radio/(arco_total/len(df)), 0) calculates
for i=1, as example,

arco_radio=math.pi*int(ratio+i)=7Ο€
round(7Ο€/(30Ο€/5), 0)=round(7Ο€/6Ο€, 0)=round(7/6, 0)

Am I right?

Hi @empet , this is the initial proposal for the graph, in the function you can actually improve several things in the effort to make it more generic, example for any angle value (end-start), any len value (df) and adapt the size of each mark according to the parameters.
parliament_design

1 Like

@U-Danny The posted image doesn’t explain why did you multiply with Ο€ both the numerator and the denominator of a fraction.
After a theoretical approach of a problem, the involved formulas must be implemented such that to reduce the computations/increase performance.
I apreciated the resulted plot, but at the first sight I noticed, not these multiplications, but the fact that in this code angle_total/round(arco_radio/(arco_total/len(df)), 0),
angle_total is given in degrees, while the denominator
seemed to be expressed in radians (it was Ο€ that suggested this idea,
although the formula to convert x radians to degrees is 180*x/Ο€).
That’s why I performed manually the above posted computations and deduced that Ο€ is a factor in both numerator and denumerator.

After the above remarks, I expected @U-Danny to comment and explain his code. Since he didn’t, I do it.

What is called arco_total is the sum of the lengths of arcs that form the configuration of parliament seats.
Each arc of a such configuration subtends an angle Ξ¨=angle_total (given in degrees), but has a diferent radius.
The length of an arc of the circle of radius r, that subtends an angle ΞΈ (in radians) is ΞΈ*r.
Hence in your function, parlamentary_Coord, arco_total should be arco_total += angle_total * math.pi* (ratio+i)/180 (i.e. the degrees must be transformed into radians to be valid the formula of arc length). Since in your code it is just arco_total += math.pi* (ratio+i), it means that you performed the right computation only when arco_total=180
Analogously, arco_radio should be arco_radio = angle_total*math.pi * (ratio+i)/180 (this is the length of the arc that subtends the angle angle_total and has the radius ratio+i (ratio is not an intuitive name for its significance, as the difference of two consecutive radii in the parliamentary configuration).

Now let us see why your wrong math computations worked even if they were performed by your code only for angle_total=180. Because of the same arguments I gave before:
angle_total * math.pi/180 is a common factor in the fraction that appears as argument of round, and as a consequence it is simplified and finally, for a good implementation
is sufficient to define:
arco_total += (ratio+i)
arco_radio=ratio+i

with the lines of code:

for a in range(len(angles)):
    current_angle = angles[a]/2
    for i in range(int(round(angle_total/angles[a],0))):        
    coord.append((ratio+a, current_angle))
    current_angle += angles[a]

simplified, as well, your function gets:

def parlamentary_Coord(df, angle_total=180, rows=4, ratio=6, initial='NAME'):    
    arco_total = 0
    angles = []
    for i in range(rows):
        arco_total += ratio+i
    for i in range(rows):
        arco_radio = ratio+i
        angles.append(angle_total/round(arco_radio/(arco_total/len(df)),0))
    coord = []
    for k, angle in enumerate(angles):
        current_angle = angle/2
        N = int(round(angle_total/angle, 0))
        for i in range(N):        
            coord.append((ratio+k, current_angle))
            current_angle += angle
    coord = Sort_Tuple(coord)    
    df["radio"] = list(zip(*coord))[0]
    df["tetha"] = list(zip(*coord))[1]
    df["INITIAL"] = df[initial].apply(lambda x: x[0]) # only for text in marker chart
    return df
1 Like