cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Check out the JMP® Marketplace featured Capability Explorer add-in
Choose Language Hide Translation Bar
mgerusdurand
Level IV

Pie Chart Labels organisation

Hello,

I try to dig in the community to find my answer but couldn't.
I have a script generating monthly report as a pie chart. I moved the labels on first one to make them readable and copy the position in my script. This is working when using the same data but the répartition of data is not the same over months so labels become overlapping again.
Is there a way to have connecting lines between the pie section and data so they can be spread over the pie while staying visible, without overlapping? I am using JMP 16 to 18.
Thanks for your help
MGD
2 ACCEPTED SOLUTIONS

Accepted Solutions
jthi
Super User

Re: Pie Chart Labels organisation

Too bad you cannot get rid of the pie chart (most overused and usually bad way to visualize anything). Below is script which might give some idea what you could do, the calculations definitely aren't optimal but they might work (I don't really remember unit circle and trigonometry anymore)

Names Default To Here(1); 

ADJUSTED_LOCATION = 0.8 ; // default is 1

dt = open("$SAMPLE_DATA/Big Class.jmp");

gb = dt << Graph Builder(
	Variables(X(:age), Y(:height)),
	Elements(Pie(X, Y, Legend(2), Summary Statistic("Sum"), Label("Label by Value")))
);

fb = Report(gb)[FrameBox(1)];
pie = fb << Find Seg("Pie Seg");
{x_origo, y_origo} = pie << Get Origin;
radius = pie << Get Radius;

Summarize(dt, groups = By(:age), sums = Sum(:height)); // we must know summary statistic used in pie chart
r_sections = Reverse((sums / Sum(sums)) * 2 * Pi());

angles = {};
For Each({section, idx}, r_sections,
	angle = (section + Pi()) / 2;
	If(idx > 1,
		angle = angle + Sum(r_sections[1::idx - 1]);
	);
	Insert Into(angles, angle);
);


label_locations = {};
For Each({angle, idx}, angles,
	i = N Items(angles) - idx;
	xcoord = x_origo + ADJUSTED_LOCATION * Cos(angle);
	ycoord = y_origo + ADJUSTED_LOCATION * Sin(angle);
	
	location = Eval List({i, xcoord, ycoord});
	Insert Into(label_locations, Eval List({location}));
);

For Each({location}, label_locations,
	Eval(EvalExpr(
		pie << Set Label Offset(Expr(location));
	));
);

default

jthi_2-1711272611572.png

ADJUSTED_LOCATION = 0.8

jthi_0-1711272568085.png

ADJUSTED_LOCATION = 1.1

jthi_1-1711272587102.png

 

This could be turned into a function which would be able to add lines to the labels.

-Jarmo

View solution in original post

jthi
Super User

Re: Pie Chart Labels organisation

Example how lines could be added

Names Default To Here(1); 

ADJUSTED_LOCATION = 1 ; // default is 1

dt = open("$SAMPLE_DATA/Big Class.jmp");

gb = dt << Graph Builder(
	Variables(X(:age), Y(:height)),
	Elements(Pie(X, Y, Legend(2), Summary Statistic("Sum"), Label("Label by Value")))
);

calculate_pie_graph_angles = Function({gb}, {Default Local},
	dt = gb << Get Data Table;
	
	// Get X and Y columns (doesn't currently support Overlay)
	vars = gb << Get Variables;
	xcol = Empty();
	ycol = Empty();
	For Each({var}, vars,
		role = var["Role"];
		If(role == "X",
			xcol = Arg(var, 1) << get name; // I don't use references
		, role == "Y",
			ycol = Arg(var, 1) << get name; // I don't use references
		);
	);

	// Get summary statistic
	elements = gb << Get Elements(1, 1);
	summary_stat = Try(elements[1]["Summary Statistic"], "Mean"); // We assume Mean is the default one

	// Calculate groups and segments
	// I'm lazy and use Evil Parse here
	stat = Eval Insert("^summary_stat^(Eval(ycol))");
	Eval(EvalExpr(
		dt_summary = dt << Summary(
			Group(Eval(xcol)),
			Expr(Parse(stat)),
			Freq("None"),
			Weight("None"),
			statistics column name format("column"),
			Link to original data table(0),
			invisible
		);
	));

	groups = dt_summary[0, 1]; // 2 is N Rows
	stats = dt_summary[0, 3];
	Close(dt_summary, no save);

	// calculate segments
	r_sections = Reverse((stats / Sum(stats)) * 2 * Pi());

	angles = {};
	For Each({section, idx}, r_sections,
		angle = (section + Pi()) / 2;
		If(idx > 1,
			angle = angle + Sum(r_sections[1::idx - 1]);
		);
		Insert Into(angles, angle);
	);
	
	Return(Reverse(angles));
);


get_segment_locations = Function({pie, angles, radius = 1}, {Default Local},
	{x_origo, y_origo} = pie << Get Origin;
	pie_radius = pie << Get Radius;

	label_locations = {};
	For Each({angle, idx}, angles,
		i = idx - 1;
		xcoord = x_origo + radius * Cos(angle);
		ycoord = y_origo + radius * Sin(angle);
		
		location = Eval List({i, xcoord, ycoord});
		Insert Into(label_locations, Eval List({location}));
	);
	
	return(label_locations);
);

fb = Report(gb)[FrameBox(1)];
pie = fb << Find Seg("Pie Seg");

angles = calculate_pie_graph_angles(gb);
offsets1 = get_segment_locations(pie, angles, 1.05);

For Each({location}, offsets,
	Eval(EvalExpr(
		pie << Set Label Offset(Expr(location));
	));
);

offsets2 = get_segment_locations(pie, angles, 0.9);
offsets3 = get_segment_locations(pie, angles, 1.02);
For Each({{start, end}}, Across(offsets2, offsets3),
	Eval(EvalExpr(
		x = start[2] || end[2];
		y = start[3] || end[3];
		fb << Add Graphics Script(
			Line(
				Expr(x), Expr(y)
			);
		);
	));
);

jthi_0-1711274953677.png

-Jarmo

View solution in original post

9 REPLIES 9
jthi
Super User

Re: Pie Chart Labels organisation

I don't know of a way of adding such lines to pie chart easily. You could calculate those lines and add them with a script or perform some calculations and set the offsets by script.

-Jarmo
mgerusdurand
Level IV

Re: Pie Chart Labels organisation

Thanks @jthi.
As my data are changing every month and all categories are not always present it is hard for me to code all of this, even the offset mais not be for every month.
As an alternative I can put the labels on sections of pie chart and alternate the distance to the center of the pie to ensure they are not overlapping. Do you know if the option existes or do I need to script that?
Thanks again
MGD
jthi
Super User

Re: Pie Chart Labels organisation

I would most likely use something else than Pie Chart (much easier than start scripting Pie segments, even the documentation in Scripting Index is wrong in most of the cases for that...), but this seems like a nice challenge so I'll try to write some sort of a script for this.

-Jarmo
mgerusdurand
Level IV

Re: Pie Chart Labels organisation

Thanks a lot. I can try too but I think you will be much faster than me.
I tried to make my supervisor change her mind for another graphic representation but she wants a pie chart unfortunately , this is not my favourite.
I will write something on the wish list too because it is true that scripting index is not good for this.

Thanks again
MGD
jthi
Super User

Re: Pie Chart Labels organisation

Too bad you cannot get rid of the pie chart (most overused and usually bad way to visualize anything). Below is script which might give some idea what you could do, the calculations definitely aren't optimal but they might work (I don't really remember unit circle and trigonometry anymore)

Names Default To Here(1); 

ADJUSTED_LOCATION = 0.8 ; // default is 1

dt = open("$SAMPLE_DATA/Big Class.jmp");

gb = dt << Graph Builder(
	Variables(X(:age), Y(:height)),
	Elements(Pie(X, Y, Legend(2), Summary Statistic("Sum"), Label("Label by Value")))
);

fb = Report(gb)[FrameBox(1)];
pie = fb << Find Seg("Pie Seg");
{x_origo, y_origo} = pie << Get Origin;
radius = pie << Get Radius;

Summarize(dt, groups = By(:age), sums = Sum(:height)); // we must know summary statistic used in pie chart
r_sections = Reverse((sums / Sum(sums)) * 2 * Pi());

angles = {};
For Each({section, idx}, r_sections,
	angle = (section + Pi()) / 2;
	If(idx > 1,
		angle = angle + Sum(r_sections[1::idx - 1]);
	);
	Insert Into(angles, angle);
);


label_locations = {};
For Each({angle, idx}, angles,
	i = N Items(angles) - idx;
	xcoord = x_origo + ADJUSTED_LOCATION * Cos(angle);
	ycoord = y_origo + ADJUSTED_LOCATION * Sin(angle);
	
	location = Eval List({i, xcoord, ycoord});
	Insert Into(label_locations, Eval List({location}));
);

For Each({location}, label_locations,
	Eval(EvalExpr(
		pie << Set Label Offset(Expr(location));
	));
);

default

jthi_2-1711272611572.png

ADJUSTED_LOCATION = 0.8

jthi_0-1711272568085.png

ADJUSTED_LOCATION = 1.1

jthi_1-1711272587102.png

 

This could be turned into a function which would be able to add lines to the labels.

-Jarmo
jthi
Super User

Re: Pie Chart Labels organisation

Example how lines could be added

Names Default To Here(1); 

ADJUSTED_LOCATION = 1 ; // default is 1

dt = open("$SAMPLE_DATA/Big Class.jmp");

gb = dt << Graph Builder(
	Variables(X(:age), Y(:height)),
	Elements(Pie(X, Y, Legend(2), Summary Statistic("Sum"), Label("Label by Value")))
);

calculate_pie_graph_angles = Function({gb}, {Default Local},
	dt = gb << Get Data Table;
	
	// Get X and Y columns (doesn't currently support Overlay)
	vars = gb << Get Variables;
	xcol = Empty();
	ycol = Empty();
	For Each({var}, vars,
		role = var["Role"];
		If(role == "X",
			xcol = Arg(var, 1) << get name; // I don't use references
		, role == "Y",
			ycol = Arg(var, 1) << get name; // I don't use references
		);
	);

	// Get summary statistic
	elements = gb << Get Elements(1, 1);
	summary_stat = Try(elements[1]["Summary Statistic"], "Mean"); // We assume Mean is the default one

	// Calculate groups and segments
	// I'm lazy and use Evil Parse here
	stat = Eval Insert("^summary_stat^(Eval(ycol))");
	Eval(EvalExpr(
		dt_summary = dt << Summary(
			Group(Eval(xcol)),
			Expr(Parse(stat)),
			Freq("None"),
			Weight("None"),
			statistics column name format("column"),
			Link to original data table(0),
			invisible
		);
	));

	groups = dt_summary[0, 1]; // 2 is N Rows
	stats = dt_summary[0, 3];
	Close(dt_summary, no save);

	// calculate segments
	r_sections = Reverse((stats / Sum(stats)) * 2 * Pi());

	angles = {};
	For Each({section, idx}, r_sections,
		angle = (section + Pi()) / 2;
		If(idx > 1,
			angle = angle + Sum(r_sections[1::idx - 1]);
		);
		Insert Into(angles, angle);
	);
	
	Return(Reverse(angles));
);


get_segment_locations = Function({pie, angles, radius = 1}, {Default Local},
	{x_origo, y_origo} = pie << Get Origin;
	pie_radius = pie << Get Radius;

	label_locations = {};
	For Each({angle, idx}, angles,
		i = idx - 1;
		xcoord = x_origo + radius * Cos(angle);
		ycoord = y_origo + radius * Sin(angle);
		
		location = Eval List({i, xcoord, ycoord});
		Insert Into(label_locations, Eval List({location}));
	);
	
	return(label_locations);
);

fb = Report(gb)[FrameBox(1)];
pie = fb << Find Seg("Pie Seg");

angles = calculate_pie_graph_angles(gb);
offsets1 = get_segment_locations(pie, angles, 1.05);

For Each({location}, offsets,
	Eval(EvalExpr(
		pie << Set Label Offset(Expr(location));
	));
);

offsets2 = get_segment_locations(pie, angles, 0.9);
offsets3 = get_segment_locations(pie, angles, 1.02);
For Each({{start, end}}, Across(offsets2, offsets3),
	Eval(EvalExpr(
		x = start[2] || end[2];
		y = start[3] || end[3];
		fb << Add Graphics Script(
			Line(
				Expr(x), Expr(y)
			);
		);
	));
);

jthi_0-1711274953677.png

-Jarmo
mgerusdurand
Level IV

Re: Pie Chart Labels organisation

Wow you were so fast!
Thanks a lot Jarmo this do the job perfectly
MGD
dlehman1
Level V

Re: Pie Chart Labels organisation

Echoing the other comments, don't use pie charts - I'm more emphatic than others on this point.  But here is a very good presentation illustrating why it is always better to use a bar chart (or similar alternatives) than a pie chart:  https://speakerdeck.com/cherdarchuk/data-looks-better-naked-pie-chart-edition.  I'd view this a chance to educate your supervisor.

mgerusdurand
Level IV

Re: Pie Chart Labels organisation

Hello @dlehman1,

Thanks for the link. I will try to educate them but they have pretty strong feelings for pie chart. I will make the parallel with other visualisation to try to convince them.
MGD