Skip to content

Commit

Permalink
Small Pie Chart enhancements (#412)
Browse files Browse the repository at this point in the history
* Added ImPlotPieChartFlags_IgnoreHidden flag. Added PlotPieChart overload that accepts an ImPlotFormatter.

* Fixed a potential division by zero in PlotPieChart.

* Removed PlotPieChart overload code duplication
  • Loading branch information
Ben1138 authored Sep 26, 2023
1 parent 538a306 commit 22ef01e
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 40 deletions.
6 changes: 4 additions & 2 deletions implot.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,9 @@ enum ImPlotInfLinesFlags_ {

// Flags for PlotPieChart
enum ImPlotPieChartFlags_ {
ImPlotPieChartFlags_None = 0, // default
ImPlotPieChartFlags_Normalize = 1 << 10 // force normalization of pie chart values (i.e. always make a full circle if sum < 0)
ImPlotPieChartFlags_None = 0, // default
ImPlotPieChartFlags_Normalize = 1 << 10, // force normalization of pie chart values (i.e. always make a full circle if sum < 0)
ImPlotPieChartFlags_IgnoreHidden = 1 << 11 // ignore hidden slices when drawing the pie chart (as if they were not there)
};

// Flags for PlotHeatmap
Expand Down Expand Up @@ -892,6 +893,7 @@ IMPLOT_TMP void PlotStems(const char* label_id, const T* xs, const T* ys, int co
IMPLOT_TMP void PlotInfLines(const char* label_id, const T* values, int count, ImPlotInfLinesFlags flags=0, int offset=0, int stride=sizeof(T));

// Plots a pie chart. Center and radius are in plot units. #label_fmt can be set to nullptr for no labels.
IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, ImPlotFormatter fmt, void* fmt_data=nullptr, double angle0=90, ImPlotPieChartFlags flags=0);
IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* label_fmt="%.1f", double angle0=90, ImPlotPieChartFlags flags=0);

// Plots a 2D heatmap chart. Values are expected to be in row-major order by default. Leave #scale_min and scale_max both at 0 for automatic color scaling, or set them to a predefined range. #label_fmt can be set to nullptr for no labels.
Expand Down
6 changes: 2 additions & 4 deletions implot_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,8 @@ void Demo_PieCharts() {
static ImPlotPieChartFlags flags = 0;
ImGui::SetNextItemWidth(250);
ImGui::DragFloat4("Values", data1, 0.01f, 0, 1);
if ((data1[0] + data1[1] + data1[2] + data1[3]) < 1) {
ImGui::SameLine();
CHECKBOX_FLAG(flags,ImPlotPieChartFlags_Normalize);
}
CHECKBOX_FLAG(flags, ImPlotPieChartFlags_Normalize);
CHECKBOX_FLAG(flags, ImPlotPieChartFlags_IgnoreHidden);

if (ImPlot::BeginPlot("##Pie1", ImVec2(250,250), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) {
ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations);
Expand Down
132 changes: 98 additions & 34 deletions implot_items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2216,57 +2216,121 @@ IMPLOT_INLINE void RenderPieSlice(ImDrawList& draw_list, const ImPlotPoint& cent
}

template <typename T>
void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* fmt, double angle0, ImPlotPieChartFlags flags) {
IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "PlotPieChart() needs to be called between BeginPlot() and EndPlot()!");
ImDrawList & draw_list = *GetPlotDrawList();
double PieChartSum(const T* values, int count, bool ignore_hidden) {
double sum = 0;
for (int i = 0; i < count; ++i)
sum += (double)values[i];
const bool normalize = ImHasFlag(flags,ImPlotPieChartFlags_Normalize) || sum > 1.0;
ImPlotPoint center(x,y);
PushPlotClipRect();
if (ignore_hidden) {
ImPlotContext& gp = *GImPlot;
ImPlotItemGroup& Items = *gp.CurrentItems;
for (int i = 0; i < count; ++i) {
if (i >= Items.GetItemCount())
break;

ImPlotItem* item = Items.GetItemByIndex(i);
IM_ASSERT(item != nullptr);
if (item->Show) {
sum += (double)values[i];
}
}
}
else {
for (int i = 0; i < count; ++i) {
sum += (double)values[i];
}
}
return sum;
}

template <typename T>
void PlotPieChartEx(const char* const label_ids[], const T* values, int count, ImPlotPoint center, double radius, double angle0, ImPlotPieChartFlags flags) {
ImDrawList& draw_list = *GetPlotDrawList();

const bool ignore_hidden = ImHasFlag(flags, ImPlotPieChartFlags_IgnoreHidden);
const double sum = PieChartSum(values, count, ignore_hidden);
const bool normalize = ImHasFlag(flags, ImPlotPieChartFlags_Normalize) || sum > 1.0;

double a0 = angle0 * 2 * IM_PI / 360.0;
double a1 = angle0 * 2 * IM_PI / 360.0;
ImPlotPoint Pmin = ImPlotPoint(x-radius,y-radius);
ImPlotPoint Pmax = ImPlotPoint(x+radius,y+radius);
ImPlotPoint Pmin = ImPlotPoint(center.x - radius, center.y - radius);
ImPlotPoint Pmax = ImPlotPoint(center.x + radius, center.y + radius);
for (int i = 0; i < count; ++i) {
double percent = normalize ? (double)values[i] / sum : (double)values[i];
a1 = a0 + 2 * IM_PI * percent;
if (BeginItemEx(label_ids[i], FitterRect(Pmin,Pmax))) {
ImU32 col = GetCurrentItem()->Color;
if (percent < 0.5) {
RenderPieSlice(draw_list, center, radius, a0, a1, col);
}
else {
RenderPieSlice(draw_list, center, radius, a0, a0 + (a1 - a0) * 0.5, col);
RenderPieSlice(draw_list, center, radius, a0 + (a1 - a0) * 0.5, a1, col);
ImPlotItem* item = GetItem(label_ids[i]);

const double percent = normalize ? (double)values[i] / sum : (double)values[i];
const bool skip = sum <= 0.0 || (ignore_hidden && item != nullptr && !item->Show);
if (!skip)
a1 = a0 + 2 * IM_PI * percent;

if (BeginItemEx(label_ids[i], FitterRect(Pmin, Pmax))) {
if (sum > 0.0) {
ImU32 col = GetCurrentItem()->Color;
if (percent < 0.5) {
RenderPieSlice(draw_list, center, radius, a0, a1, col);
}
else {
RenderPieSlice(draw_list, center, radius, a0, a0 + (a1 - a0) * 0.5, col);
RenderPieSlice(draw_list, center, radius, a0 + (a1 - a0) * 0.5, a1, col);
}
}
EndItem();
}
a0 = a1;
if (!skip)
a0 = a1;
}
}

int PieChartFormatter(double value, char* buff, int size, void* data) {
const char* fmt = (const char*)data;
return snprintf(buff, size, fmt, value);
};

template <typename T>
void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* fmt, double angle0, ImPlotPieChartFlags flags) {
PlotPieChart<T>(label_ids, values, count, x, y, radius, PieChartFormatter, (void*)fmt, angle0, flags);
}
#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotPieChart<T>(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* fmt, double angle0, ImPlotPieChartFlags flags);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

template <typename T>
void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, ImPlotFormatter fmt, void* fmt_data, double angle0, ImPlotPieChartFlags flags) {
IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "PlotPieChart() needs to be called between BeginPlot() and EndPlot()!");
ImDrawList& draw_list = *GetPlotDrawList();

const bool ignore_hidden = ImHasFlag(flags, ImPlotPieChartFlags_IgnoreHidden);
const double sum = PieChartSum(values, count, ignore_hidden);
const bool normalize = ImHasFlag(flags, ImPlotPieChartFlags_Normalize) || sum > 1.0;
ImPlotPoint center(x, y);

PushPlotClipRect();
PlotPieChartEx(label_ids, values, count, center, radius, angle0, flags);
if (fmt != nullptr) {
a0 = angle0 * 2 * IM_PI / 360.0;
a1 = angle0 * 2 * IM_PI / 360.0;
double a0 = angle0 * 2 * IM_PI / 360.0;
double a1 = angle0 * 2 * IM_PI / 360.0;
char buffer[32];
for (int i = 0; i < count; ++i) {
ImPlotItem* item = GetItem(label_ids[i]);
double percent = normalize ? (double)values[i] / sum : (double)values[i];
a1 = a0 + 2 * IM_PI * percent;
if (item->Show) {
ImFormatString(buffer, 32, fmt, (double)values[i]);
ImVec2 size = ImGui::CalcTextSize(buffer);
double angle = a0 + (a1 - a0) * 0.5;
ImVec2 pos = PlotToPixels(center.x + 0.5 * radius * cos(angle), center.y + 0.5 * radius * sin(angle),IMPLOT_AUTO,IMPLOT_AUTO);
ImU32 col = CalcTextColor(ImGui::ColorConvertU32ToFloat4(item->Color));
draw_list.AddText(pos - size * 0.5f, col, buffer);
IM_ASSERT(item != nullptr);

const double percent = normalize ? (double)values[i] / sum : (double)values[i];
const bool skip = ignore_hidden && item != nullptr && !item->Show;

if (!skip) {
a1 = a0 + 2 * IM_PI * percent;
if (item->Show) {
fmt((double)values[i], buffer, 32, fmt_data);
ImVec2 size = ImGui::CalcTextSize(buffer);
double angle = a0 + (a1 - a0) * 0.5;
ImVec2 pos = PlotToPixels(center.x + 0.5 * radius * cos(angle), center.y + 0.5 * radius * sin(angle), IMPLOT_AUTO, IMPLOT_AUTO);
ImU32 col = CalcTextColor(ImGui::ColorConvertU32ToFloat4(item->Color));
draw_list.AddText(pos - size * 0.5f, col, buffer);
}
a0 = a1;
}
a0 = a1;
}
}
PopPlotClipRect();
}
#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotPieChart<T>(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* fmt, double angle0, ImPlotPieChartFlags flags);
#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, ImPlotFormatter fmt, void* fmt_data, double angle0, ImPlotPieChartFlags flags);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

Expand Down

0 comments on commit 22ef01e

Please sign in to comment.