Gtk Pie Chart Widget


I needed a pie chart widget for a project so I made one. I used cairo for rendering of course. This API has add_segment and remove_segment functions but I will change that to take a tree model. There are almost no options or style at this point which would obviously be important but I have got the basic outline down I think. I am considering writing an entire charting widget set. There are several packages focused on plotting but they are not presentation oriented.

Code Follows:

/*
 * Gtk Pie Chart Widget
 * Copyright (C) 2008 Aron Rubin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; version 2
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 */
 
#ifndef GTK_PIE_CHART_H_INCLUDED
#define GTK_PIE_CHART_H_INCLUDED
 
#include <gdk/gdk.h>
#include <gtk/gtkwidget.h>
 
G_BEGIN_DECLS
 
#define GTK_PIE_CHART(obj)	   GTK_CHECK_CAST( obj, gtk_pie_chart_get_type(), GtkPieChart )
#define GTK_PIE_CHART_CLASS(klass) GTK_CHECK_CLASS_CAST( klass, gtk_pie_chart_get_type(), GtkPieChartClass )
#define GTK_IS_PIE_CHART(obj)	   GTK_CHECK_TYPE( obj, gtk_pie_chart_get_type() )
 
typedef struct GtkPieChart GtkPieChart;
typedef struct GtkPieChartClass GtkPieChartClass;
 
typedef struct GtkPieSegment {
  float sweep;
  char *name;
  float rgba[4];
} GtkPieSegment;
 
struct GtkPieChart {
  GtkWidget base;
 
  int width;
  int height;
 
  float start_angle;
  float sweep_factor;
  float radius;
 
  GList *segments;
};
 
struct GtkPieChartClass {
  GtkWidgetClass parent_class;
};
 
GtkType gtk_pie_chart_get_type();
GtkWidget *gtk_pie_chart_new();
guint gtk_pie_chart_add_segment( GtkPieChart *pie, float sweep, const gchar *name, float *rgba );
void gtk_pie_chart_remove_segment( GtkPieChart *pie, guint id );
 
G_END_DECLS
 
#endif
/*
 * Gtk Pie Chart Widget
 * Copyright (C) 2008 Aron Rubin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; version 2
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 */
 
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <glib.h>
#include <glib-object.h>
#include <gtk/gtkwidget.h>
#include <cairo.h>
#include "gtk_pie_chart.h"
 
#define UNUSED __attribute__ ((unused))
 
// Properties
enum {
  PROP_0,
  PROP_START_ANGLE,
};
 
static GtkWidgetClass *parent_class = NULL;
 
// object signals
static void gtk_pie_chart_class_init( GtkPieChartClass *class );
static void gtk_pie_chart_init( GtkPieChart *pie );
static void gtk_pie_chart_finalize( GObject *pie );
static void gtk_pie_chart_set_property( GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec );
static void gtk_pie_chart_get_property( GObject *object, guint prop_id, GValue *value, GParamSpec *pspec );
 
// widget signals
static void gtk_pie_chart_realize( GtkWidget *pie );
static void gtk_pie_chart_size_allocate( GtkWidget *pie, GtkAllocation *allocation );
static gboolean gtk_pie_chart_expose( GtkWidget *pie, GdkEventExpose *event );
 
// internals
 
/**********************************************\
 
         Section: GtkPieChart Methods
 
\**********************************************/
 
/* ===========================================
 
      Group: GtkPieChart Core Object Methods
 
   =========================================== */
 
GtkType gtk_pie_chart_get_type() {
  static unsigned int gtk_pie_chart_type = 0;
 
  if( !gtk_pie_chart_type ) {
    GtkTypeInfo gtk_pie_chart_info = {
      "GtkPieChart",
      sizeof(GtkPieChart),
      sizeof(GtkPieChartClass),
      (GtkClassInitFunc)gtk_pie_chart_class_init,
      (GtkObjectInitFunc)gtk_pie_chart_init,
      NULL, NULL, NULL
    };
    gtk_pie_chart_type = gtk_type_unique( GTK_TYPE_WIDGET, &gtk_pie_chart_info );
  }
  return( gtk_pie_chart_type );
}
 
GtkWidget *gtk_pie_chart_new() {
  GtkPieChart *pie;
  GtkWidget *widget;
 
  pie = gtk_type_new( gtk_pie_chart_get_type() );
  widget = GTK_WIDGET(pie);
  widget->style = gtk_style_new();
 
  return widget;
}
 
static void gtk_pie_chart_class_init( GtkPieChartClass *class ) {
  GObjectClass *obj_class;
  GtkWidgetClass *wid_class;
 
  obj_class = (GObjectClass *)class;
  wid_class = (GtkWidgetClass *)class;
  parent_class = gtk_type_class( gtk_widget_get_type() );
 
  obj_class->finalize = gtk_pie_chart_finalize;
  obj_class->set_property = gtk_pie_chart_set_property;
  obj_class->get_property = gtk_pie_chart_get_property;
 
  wid_class->realize = gtk_pie_chart_realize;
  wid_class->size_allocate = gtk_pie_chart_size_allocate;
  wid_class->expose_event = gtk_pie_chart_expose;
 
  g_object_class_install_property( obj_class, PROP_START_ANGLE,
                                  g_param_spec_double( "start_angle", "Start Angle",
						       "The angle of the leading edge of the first segment.",
						       -M_PI, M_PI, 0, G_PARAM_READWRITE ) );
 
}
 
static void gtk_pie_chart_init( GtkPieChart *pie ) {
 
}
 
static void gtk_pie_chart_finalize( GObject *pie ) {
 
}
 
// GObject Methods
 
static void gtk_pie_chart_set_property( GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec UNUSED ) {
  GtkPieChart *pie;
 
  pie = GTK_PIE_CHART(object);
 
  switch( prop_id ) {
  case PROP_START_ANGLE: pie->start_angle = g_value_get_double( value ); break;
  default:
    break;
  }
}
 
static void gtk_pie_chart_get_property( GObject *object, guint prop_id, GValue *value, GParamSpec *pspec ) {
  GtkPieChart *pie;
 
  pie = GTK_PIE_CHART(object);
 
  switch( prop_id ) {
  case PROP_START_ANGLE: g_value_set_int( value, pie->start_angle ); break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
    break;
  }
}
 
static void gtk_pie_chart_set_size( GtkPieChart *pie, int width, int height ) {
  pie->width = width;
  pie->height = height;
 
  pie->radius = fmin( width, height )*0.33;
}
 
static void gtk_pie_chart_update_segments( GtkPieChart *pie ) {
  float total_sweep = 0.0;
  gboolean divide_remaining = FALSE;
  GList *list;
 
  g_return_if_fail( GTK_IS_PIE_CHART(pie) );
 
  list = pie->segments;
  while( list ) {
    if( ((GtkPieSegment *)list->data)->sweep > 0.0 )
      total_sweep += ((GtkPieSegment *)list->data)->sweep;
    else
      divide_remaining = TRUE;
    list = list->next;
  }
 
  if( total_sweep > 0.0 )
    pie->sweep_factor = 100.0/total_sweep;
  else
    pie->sweep_factor = 1.0;
}
 
guint gtk_pie_chart_add_segment( GtkPieChart *pie, float sweep, const gchar *name, float *rgba ) {
  GtkPieSegment *segment;
 
  g_return_val_if_fail( GTK_IS_PIE_CHART(pie), 0 );
 
  segment = g_new0( GtkPieSegment, 1 );
  g_return_val_if_fail( segment, 0 );
  segment->sweep = sweep;
  segment->name = g_strdup( name );
  if( rgba )
    memcpy( segment->rgba, rgba, sizeof(float)*4 );
  pie->segments = g_list_append( pie->segments, segment );
 
  gtk_pie_chart_update_segments( pie );
 
  gtk_widget_queue_draw( (GtkWidget *)pie );
 
  return( g_list_length( pie->segments ) );
}
 
void gtk_pie_chart_remove_segment( GtkPieChart *pie, guint id ) {
  GtkPieSegment *segment;
  GList *segment_item;
 
  g_return_if_fail( GTK_IS_PIE_CHART(pie) );
 
  segment_item = g_list_nth( pie->segments, id );
  g_return_if_fail( segment_item && segment_item->data );
  segment = (GtkPieSegment *)segment_item->data;
 
  if( segment->name )
    g_free( segment->name );
 
  pie->segments = g_list_delete_link( pie->segments, segment_item );
  g_free( segment );
 
  gtk_widget_queue_draw( (GtkWidget *)pie );
}
 
static void gtk_pie_chart_realize( GtkWidget *widget ) {
  GtkPieChart *pie;
  GdkWindowAttr attributes;
  int attributes_mask = 0;
 
  g_return_if_fail( widget );
  g_return_if_fail( GTK_IS_PIE_CHART(widget) );
 
  pie = GTK_PIE_CHART(widget);
  GTK_WIDGET_SET_FLAGS( widget, GTK_REALIZED );
 
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
 
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask = gtk_widget_get_events( widget ) | GDK_EXPOSURE_MASK;
  attributes.visual = gtk_widget_get_visual( widget );
  attributes.colormap = gtk_widget_get_colormap( widget );
 
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
  widget->window = gdk_window_new( widget->parent->window, &attributes, attributes_mask );
 
  widget->style = gtk_style_attach( widget->style, widget->window );
 
  gdk_window_set_user_data( widget->window, widget );
  gdk_window_set_background( widget->window, widget->style->bg );
 
  gtk_pie_chart_set_size( pie, widget->allocation.width, widget->allocation.height );
}
 
static void gtk_pie_chart_size_allocate( GtkWidget *widget, GtkAllocation *allocation ) {
  GtkPieChart *pie;
 
  g_return_if_fail( widget );
  g_return_if_fail( GTK_IS_PIE_CHART(widget) );
  g_return_if_fail( allocation );
 
  pie = GTK_PIE_CHART(widget);
 
  widget->allocation = *allocation;
  if( GTK_WIDGET_REALIZED(widget) )
    gdk_window_move_resize( widget->window, allocation->x, allocation->y, allocation->width, allocation->height );
 
  gtk_pie_chart_set_size( pie, widget->allocation.width, widget->allocation.height );
}
 
static float gtk_pie_chart_draw_segment( GtkPieChart *pie, cairo_t *cr, GtkPieSegment *segment, float start ) {
  float end = start + /* pie->sweep_factor* */ segment->sweep*(2.0*M_PI);
  float mid = (start + end)*0.5;
 
  cairo_save( cr );
  //cairo_move_to( cr, pie->radius*cos( start*2*pi ), pie->radius*sin( start*(2.0*M_PI) ) )
  cairo_move_to( cr, pie->width/2, pie->height/2 );
  cairo_line_to( cr, pie->width/2 + pie->radius*cos( start ), pie->height/2 + pie->radius*sin( start ) );
  cairo_arc( cr, pie->width/2, pie->height/2, pie->radius, start, end );
  //cairo_arc_negative( cr, pie->width/2, pie->height/2, 0, end*(2.0*M_PI), start*(2.0*M_PI) );
  cairo_close_path( cr );
 
  cairo_set_line_width( cr, 1.0 );
  cairo_set_source_rgba( cr, segment->rgba[0], segment->rgba[1], segment->rgba[2], segment->rgba[3]  );
  cairo_fill_preserve( cr );
  cairo_set_source_rgba( cr, 0, 0, 0, 1.0 );
  cairo_stroke( cr );
 
  cairo_move_to( cr, pie->width/2 + 0.5*pie->radius*cos( mid ), pie->height/2 + 0.5*pie->radius*sin( mid ) );
  cairo_line_to( cr, pie->width/2 + 1.3*pie->radius*cos( mid ), pie->height/2 + 1.3*pie->radius*sin( mid ) );
  if( mid > M_PI_2 && mid < (M_PI_2 + M_PI) )
    cairo_line_to( cr, pie->width/6, pie->height/2 + 1.3*pie->radius*sin( mid ) );
  else
    cairo_line_to( cr, pie->width*5/6, pie->height/2 + 1.3*pie->radius*sin( mid ) );
  cairo_stroke( cr );
  cairo_restore( cr );
 
  cairo_save( cr );
  if( mid > M_PI_2 && mid < (M_PI_2 + M_PI) ) {
    cairo_text_extents_t text_extents = { 0, 0, 0, 0, 0, 0 };
    cairo_text_extents( cr, segment->name ? segment->name : "", &text_extents );
    cairo_move_to( cr, pie->width/6 - text_extents.width, pie->height/2 + 1.3*pie->radius*sin( mid ) + 4.0 );
  } else {
    cairo_move_to( cr, pie->width*5/6, pie->height/2 + 1.3*pie->radius*sin( mid ) + 4.0 );
  }
  cairo_set_source_rgba( cr, 0, 0, 0, 1.0 );
  cairo_select_font_face( cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_set_font_size( cr, 10.0 );
  cairo_show_text( cr, segment->name ? segment->name : "" );
  cairo_restore( cr );
 
  return( end );
}
 
static int gtk_pie_chart_expose( GtkWidget *widget, GdkEventExpose *event ) {
  GtkPieChart *pie;
  cairo_t *cr;
  //GdkRectangle rect = { 0, 0, 0, 0 };
  GList *list;
  float angle;
 
  g_return_val_if_fail( widget, FALSE);
  g_return_val_if_fail( GTK_IS_PIE_CHART(widget), FALSE );
  pie = GTK_PIE_CHART(widget);
 
  cr = gdk_cairo_create( widget->window );
 
  cairo_save( cr ); // expose area
 
  cairo_rectangle( cr, event->area.x, event->area.y, event->area.width, event->area.height );
  cairo_clip( cr );
 
  angle = pie->start_angle;
  list = pie->segments;
  while( list ) {
    GtkPieSegment *segment = (GtkPieSegment *)list->data;
    angle = gtk_pie_chart_draw_segment( pie, cr, segment, angle );
    list = list->next;
  }
 
  cairo_restore( cr ); // expose area
 
  cairo_destroy( cr );
 
  return FALSE;
}

, , , , , ,

  1. #1 by Stacy Teet on July 15, 2008 - 2:58 pm

    Great widget! I was wondering if you made any progress in the series? I need a pie chart widget that I can customize how many slices and enter in text and a simple + or – result for each slice. Think I could do that with your widget?

    Thanks in advance

  2. #2 by A R Baboon on July 15, 2008 - 9:26 pm

    Sure I have a couple of fixes. I am not entirely sure what its meant by + or – but it sounds like it would not be an issue.

  3. #3 by Haneef on May 19, 2009 - 1:08 am

    Can u please expose sample app which uses above functionalities

  4. #4 by A R Baboon on May 19, 2009 - 5:53 pm

    Sure:

    #include <stdlib.h>
    #include <math.h>
    #include <glib.h>
    #include <gtk/gtk.h>
    #include "gtk_pie_chart.h"
     
     
    gboolean rotate_chart_tcb( GtkPieChart *pie ) {
      static unsigned long count = 0;
      double angle;
     
      angle = (M_PI*2.0/100.0)*(count%100) - M_PI;
      g_object_set( pie, "start_angle", angle, NULL );
     
      count++;
     
      gtk_widget_queue_draw( (GtkWidget *)pie );
     
      return( TRUE );
    }
     
     
    void quit () {
      gtk_exit( 0 );
    }
     
     
    int main( int argc, char *argv[] ) {
      GtkWidget *win, *vbox, *pie;
      float rgba[4] = { 1.0, 1.0, 0.5, 1.0 };
     
      gtk_init( &argc, &argv );
     
      win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
      gtk_widget_set_name( win, "Pie Chart Test" );
     
      vbox = gtk_vbox_new( FALSE, 0 );
      gtk_container_add( GTK_CONTAINER(win), vbox );
      gtk_widget_show( vbox );
     
      gtk_signal_connect( GTK_OBJECT(win), "destroy", GTK_SIGNAL_FUNC(quit), NULL );
     
      // Create the drawing area
      pie = gtk_pie_chart_new();
      gtk_pie_chart_add_segment( GTK_PIE_CHART(pie), 0.5, "First", rgba );
      rgba[0] = 0.5;
      gtk_pie_chart_add_segment( GTK_PIE_CHART(pie), 0.25, "Second", rgba );
      rgba[1] = 0.5;
      rgba[2] = 1.0;
      gtk_pie_chart_add_segment( GTK_PIE_CHART(pie), 0.25, "Third", rgba );
     
      gtk_box_pack_start( GTK_BOX(vbox), pie, TRUE, TRUE, 0 );
     
      gtk_widget_show( pie );
     
      gtk_widget_show_all( win );
     
      g_timeout_add( 100, (GSourceFunc)rotate_chart_tcb, pie );
     
      gtk_main();
     
      exit( 0 );
    }
  5. #5 by jf on January 26, 2011 - 2:24 pm

    Nice. When i compiled got a few warnings and minor errors.

    compiled the widget with -D_GNU_SOURCE to remove math.h errors and when linking added -lm.

    Thanks for the widget.

  6. #6 by William on February 27, 2012 - 6:10 pm

    How difficult would it be alter this so it draws with GDK primitives? Would you be interested in attempting this? I’m working on a program that does not support cairo, it uses an older version of GTK.

(will not be published)


Bad Behavior has blocked 152 access attempts in the last 7 days.