/* HomeBank -- Free, easy, personal accounting for everyone.
* Copyright (C) 1995-2016 Maxime DOYEN
*
* This file is part of HomeBank.
*
* HomeBank is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* HomeBank 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include "homebank.h"
#include "gtk-chart-colors.h"
#include "gtk-chart.h"
#define MYDEBUG 0
#if MYDEBUG
#define DB(x) (x);
#else
#define DB(x);
#endif
#define HELPDRAW 0
#define DYNAMICS 1
static void gtk_chart_class_intern_init (gpointer);
static void gtk_chart_class_init (GtkChartClass *klass);
static void gtk_chart_init (GtkChart *chart);
static void gtk_chart_dispose (GObject * object);
static void gtk_chart_finalize (GObject * object);
static gboolean drawarea_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer user_data);
static void drawarea_realize_callback(GtkWidget *widget, gpointer user_data);
static gboolean drawarea_draw_callback(GtkWidget *widget, cairo_t *wcr, gpointer user_data);
static gboolean drawarea_motionnotifyevent_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
static gboolean drawarea_querytooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data);
static gboolean drawarea_full_redraw(GtkWidget *widget, gpointer user_data);
static void chart_calculation(GtkChart *chart);
static void chart_clear(GtkChart *chart);
static void colchart_first_changed( GtkAdjustment *adj, gpointer user_data);
static void colchart_compute_range(GtkChart *chart);
static void colchart_calculation(GtkChart *chart);
static void colchart_scrollbar_setvalues(GtkChart *chart);
static void piechart_calculation(GtkChart *chart);
static GdkPixbuf *create_color_pixbuf (struct rgbcol *color);
static GtkWidget *legend_list_new(GtkChart *chart);
static GtkBoxClass *gtk_chart_parent_class = NULL;
static const double dashed3[] = {3.0};
GType
gtk_chart_get_type ()
{
static GType chart_type = 0;
if (!chart_type)
{
static const GTypeInfo chart_info =
{
sizeof (GtkChartClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) gtk_chart_class_intern_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GtkChart),
0, /* n_preallocs */
(GInstanceInitFunc) gtk_chart_init,
NULL
};
chart_type = g_type_register_static (GTK_TYPE_BOX, "GtkChart",
&chart_info, 0);
}
return chart_type;
}
static void
gtk_chart_class_intern_init (gpointer klass)
{
gtk_chart_parent_class = g_type_class_peek_parent (klass);
gtk_chart_class_init ((GtkChartClass *) klass);
}
static void
gtk_chart_class_init (GtkChartClass * klass)
{
GObjectClass *gobject_class;
DB( g_print("\n[gtkchart] class init\n") );
gobject_class = G_OBJECT_CLASS (klass);
//gobject_class->get_property = gtk_chart_get_property;
//gobject_class->set_property = gtk_chart_set_property;
gobject_class->dispose = gtk_chart_dispose;
gobject_class->finalize = gtk_chart_finalize;
}
static void
gtk_chart_init (GtkChart * chart)
{
GtkWidget *widget, *vbox;
GtkWidget *scrollwin, *treeview;
DB( g_print("\n[gtkchart] init\n") );
chart->surface = NULL;
chart->nb_items = 0;
chart->items = NULL;
chart->title = NULL;
chart->pfd = NULL;
chart->abs = FALSE;
chart->dual = FALSE;
chart->barw = GTK_CHART_BARW;
chart->show_mono = FALSE;
//chart->drawmode = CHART_DRAW_FULL;
chart->active = -1;
chart->lastactive = -1;
chart->minor_rate = 1.0;
chart->timer_tag = 0;
gtk_chart_set_color_scheme(chart, CHART_COLMAP_HOMEBANK);
widget=GTK_WIDGET(chart);
gtk_box_set_homogeneous(GTK_BOX(widget), FALSE);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start (GTK_BOX (widget), vbox, TRUE, TRUE, 0);
/* drawing area */
scrollwin = gtk_frame_new(NULL);
gtk_frame_set_shadow_type (GTK_FRAME(scrollwin), GTK_SHADOW_ETCHED_IN);
gtk_box_pack_start (GTK_BOX (vbox), scrollwin, TRUE, TRUE, 0);
//scrollwin = gtk_scrolled_window_new(NULL,NULL);
//gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin), GTK_SHADOW_ETCHED_IN);
//gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
//gtk_box_pack_start (GTK_BOX (vbox), scrollwin, TRUE, TRUE, 0);
chart->drawarea = gtk_drawing_area_new();
//gtk_widget_set_double_buffered (GTK_WIDGET(widget), FALSE);
gtk_container_add( GTK_CONTAINER(scrollwin), chart->drawarea );
gtk_widget_set_size_request(chart->drawarea, 100, 100 );
gtk_widget_set_has_tooltip(chart->drawarea, TRUE);
gtk_widget_show(chart->drawarea);
#if MYDEBUG == 1
/*GtkStyle *style;
PangoFontDescription *font_desc;
g_print("draw_area font\n");
style = gtk_widget_get_style(GTK_WIDGET(chart->drawarea));
font_desc = style->font_desc;
g_print("family: %s\n", pango_font_description_get_family(font_desc) );
g_print("size: %d (%d)\n", pango_font_description_get_size (font_desc), pango_font_description_get_size (font_desc )/PANGO_SCALE );
*/
#endif
/* scrollbar */
chart->adjustment = GTK_ADJUSTMENT(gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
chart->scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT (chart->adjustment));
gtk_box_pack_start (GTK_BOX (vbox), chart->scrollbar, FALSE, TRUE, 0);
/* legend treeview */
scrollwin = gtk_scrolled_window_new(NULL,NULL);
chart->scrollwin = scrollwin;
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin), GTK_SHADOW_ETCHED_IN);
//gtk_container_set_border_width (GTK_CONTAINER(scrollwin), 5);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
treeview = legend_list_new(chart);
chart->treeview = treeview;
chart->legend = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
gtk_container_add(GTK_CONTAINER(scrollwin), treeview);
gtk_box_pack_start (GTK_BOX (widget), scrollwin, FALSE, FALSE, 0);
gtk_widget_add_events(GTK_WIDGET(chart->drawarea),
GDK_EXPOSURE_MASK |
//GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK
//GDK_BUTTON_PRESS_MASK |
//GDK_BUTTON_RELEASE_MASK
);
g_signal_connect( G_OBJECT(chart->drawarea), "configure-event", G_CALLBACK (drawarea_configure_event), chart);
g_signal_connect( G_OBJECT(chart->drawarea), "realize", G_CALLBACK(drawarea_realize_callback), chart ) ;
g_signal_connect( G_OBJECT(chart->drawarea), "draw", G_CALLBACK(drawarea_draw_callback), chart ) ;
#if DYNAMICS == 1
g_signal_connect( G_OBJECT(chart->drawarea), "query-tooltip", G_CALLBACK(drawarea_querytooltip_callback), chart );
g_signal_connect( G_OBJECT(chart->drawarea), "motion-notify-event", G_CALLBACK(drawarea_motionnotifyevent_callback), chart );
#endif
g_signal_connect (G_OBJECT(chart->adjustment), "value-changed", G_CALLBACK (colchart_first_changed), chart);
//g_signal_connect( G_OBJECT(chart->drawarea), "map-event", G_CALLBACK(chart_map), chart ) ;
//g_signal_connect( G_OBJECT(chart->drawarea), "button-press-event", G_CALLBACK(chart_button_press), chart );
//g_signal_connect( G_OBJECT(chart->drawarea), "button-release-event", G_CALLBACK(chart_button_release), chart );
}
/* --- */
GtkWidget *
gtk_chart_new (gint type)
{
GtkChart *chart;
DB( g_print("\n======================================================\n") );
DB( g_print("\n[gtkchart] new\n") );
chart = g_object_new (GTK_TYPE_CHART, NULL);
chart->type = type;
return GTK_WIDGET(chart);
}
static void
gtk_chart_dispose (GObject *gobject)
{
//GtkChart *chart = GTK_CHART (object);
DB( g_print("\n[gtkchart] dispose\n") );
/* In dispose(), you are supposed to free all types referenced from this
* object which might themselves hold a reference to self. Generally,
* the most simple solution is to unref all members on which you own a
* reference.
*/
/* dispose() might be called multiple times, so we must guard against
* calling g_object_unref() on an invalid GObject by setting the member
* NULL; g_clear_object() does this for us, atomically.
*/
//g_clear_object (&self->priv->an_object);
/* Always chain up to the parent class; there is no need to check if
* the parent class implements the dispose() virtual function: it is
* always guaranteed to do so
*/
G_OBJECT_CLASS (gtk_chart_parent_class)->dispose (gobject);
}
static void
gtk_chart_finalize (GObject * object)
{
GtkChart *chart = GTK_CHART (object);
DB( g_print("\n[gtkchart] finalize\n") );
chart_clear(chart);
if(chart->pfd)
{
pango_font_description_free (chart->pfd);
chart->pfd = NULL;
}
if (chart->surface)
{
cairo_surface_destroy (chart->surface);
chart->surface = NULL;
}
G_OBJECT_CLASS (gtk_chart_parent_class)->finalize (object);
}
/*
** print a integer number
*/
static gchar *chart_print_int(GtkChart *chart, gint value)
{
hb_strfmon_int(chart->buffer1, CHART_BUFFER_LENGTH-1, (gdouble)value, chart->kcur, chart->minor);
return chart->buffer1;
}
/*
** print a double number
*/
static gchar *chart_print_double(GtkChart *chart, gchar *buffer, gdouble value)
{
hb_strfmon(buffer, CHART_BUFFER_LENGTH-1, value, chart->kcur, chart->minor);
return buffer;
}
/*
** clear any allocated memory
*/
static void chart_clear(GtkChart *chart)
{
gint i;
DB( g_print("\n[gtkchart] clear\n") );
//free & clear any previous allocated datas
if(chart->title != NULL)
{
g_free(chart->title);
chart->title = NULL;
}
if(chart->subtitle != NULL)
{
g_free(chart->subtitle);
chart->subtitle = NULL;
}
if(chart->items != NULL)
{
for(i=0;inb_items;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
g_free(item->legend);
}
g_array_free(chart->items, TRUE);
chart->items = NULL;
}
chart->nb_items = 0;
chart->total = 0;
chart->range = 0;
chart->rawmin = 0;
chart->rawmax = 0;
chart->every_xval = 7;
chart->active = -1;
chart->lastactive = -1;
}
/*
** setup our chart with a model and column
*/
static void chart_setup_with_model(GtkChart *chart, GtkTreeModel *list_store, guint column1, guint column2)
{
gint i;
gboolean valid;
GtkTreeIter iter, l_iter;
gint color;
DB( g_print("\n[chart] setup with model\n") );
chart_clear(chart);
if( GTK_IS_LIST_STORE(chart->legend) )
gtk_list_store_clear (GTK_LIST_STORE(chart->legend));
chart->nb_items = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store), NULL);
chart->items = g_array_sized_new(FALSE, FALSE, sizeof(ChartItem), chart->nb_items);
DB( g_print(" nb=%d, struct=%d\n", chart->nb_items, sizeof(ChartItem)) );
chart->dual = (column1 == column2) ? FALSE : TRUE;
/* Get the first iter in the list */
valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(list_store), &iter);
i = 0;
while (valid)
{
gint id;
gchar *label;
gdouble value1, value2;
ChartItem item;
GdkPixbuf *pixbuf;
/* column 0: pos (gint) */
/* column 1: key (gint) */
/* column 2: label (gchar) */
/* column x: values (double) */
gtk_tree_model_get (GTK_TREE_MODEL(list_store), &iter,
0, &id,
2, &label,
column1, &value1,
column2, &value2,
-1);
if(chart->dual || chart->abs)
{
value1 = ABS(value1);
value2 = ABS(value2);
}
DB( g_print("%d: '%s' %.2f %2f\n", i, label, value1, value2) );
/* data1 value storage & min, max compute */
chart->rawmin = MIN(chart->rawmin, value1);
chart->rawmax = MAX(chart->rawmax, value1);
if( chart->dual )
{
/* data2 value storage & min, max compute */
chart->rawmin = MIN(chart->rawmin, value2);
chart->rawmax = MAX(chart->rawmax, value2);
}
item.label = label;
item.serie1 = value1;
item.serie2 = value2;
g_array_append_vals(chart->items, &item, 1);
/* ensure rawmin rawmax not equal */
if(chart->rawmin == chart->rawmax)
{
chart->rawmin = 0;
chart->rawmax = 100;
}
/* populate our legend list */
color = i % chart->color_scheme.nb_cols;
//color = id % chart->nb_cols;
//DB( g_print ("Row %d: (%s, %2.f) color %d\n", id, title, value, color) );
pixbuf = create_color_pixbuf (&chart->color_scheme.colors[color]);
gtk_list_store_append (GTK_LIST_STORE(chart->legend), &l_iter);
gtk_list_store_set (GTK_LIST_STORE(chart->legend), &l_iter,
LST_LEGEND_COLOR, pixbuf,
LST_LEGEND_TITLE, label,
LST_LEGEND_AMOUNT, value1,
-1);
/* pie chart total sum */
chart->total += ABS(value1);
valid = gtk_tree_model_iter_next (list_store, &iter);
i++;
}
// compute rate for legend for bar/pie
for(i=0;inb_items;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
item->rate = ABS(item->serie1*100/chart->total);
item->legend = g_markup_printf_escaped("%s (%.2f%%)", item->label, item->rate);
}
if( chart->type != CHART_TYPE_LINE )
{
valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(chart->legend), &iter);
while (valid)
{
gdouble amount, rate;
gtk_tree_model_get(GTK_TREE_MODEL(chart->legend), &iter,
LST_LEGEND_AMOUNT, &amount,
-1);
rate = ABS( amount*100/chart->total);
gtk_list_store_set(GTK_LIST_STORE(chart->legend), &iter,
LST_LEGEND_RATE, rate,
-1);
valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(chart->legend), &iter);
}
}
//g_print("total is %.2f\n", total);
//ensure the widget is mapped
//gtk_widget_map(chart);
}
static void chart_set_font_size(GtkChart *chart, gint font_size)
{
gint size = 10;
//DB( g_print("\n[chart] set font size\n") );
switch(font_size)
{
case CHART_FONT_SIZE_TITLE:
size = chart->pfd_size + 3;
break;
case CHART_FONT_SIZE_SUBTITLE:
size = chart->pfd_size + 1;
break;
case CHART_FONT_SIZE_NORMAL:
size = chart->pfd_size - 1;
break;
}
//DB( g_print(" size=%d\n", size) );
pango_font_description_set_size(chart->pfd, size * PANGO_SCALE);
}
/*
** recompute according to type
*/
static void chart_recompute(GtkChart *chart)
{
DB( g_print("\n[gtkchart] recompute\n") );
chart_calculation (chart);
switch(chart->type)
{
case CHART_TYPE_LINE:
case CHART_TYPE_COL:
colchart_compute_range(chart);
colchart_calculation(chart);
gtk_adjustment_set_value(chart->adjustment, 0);
colchart_scrollbar_setvalues(chart);
gtk_widget_show(chart->scrollbar);
break;
case CHART_TYPE_PIE:
piechart_calculation(chart);
gtk_widget_hide(chart->scrollbar);
break;
}
}
/* bar section */
static float CalculateStepSize(float range, float targetSteps)
{
// calculate an initial guess at step size
float tempStep = range/targetSteps;
// get the magnitude of the step size
float mag = (float)floor(log10(tempStep));
float magPow = (float)pow(10, mag);
// calculate most significant digit of the new step size
float magMsd = (int)(tempStep/magPow + 0.5);
// promote the MSD to either 1, 2, or 5
if (magMsd > 5.0)
magMsd = 10.0f;
else if (magMsd > 2.0)
magMsd = 5.0f;
else if (magMsd >= 1.0)
magMsd = 2.0f;
return magMsd*magPow;
}
static void colchart_compute_range(GtkChart *chart)
{
double lobound=chart->rawmin, hibound=chart->rawmax;
DB( g_print("\n[column] compute range\n") );
/* comptute max ticks */
chart->range = chart->rawmax - chart->rawmin;
gint maxticks = MIN(10,floor(chart->graph_height / (chart->font_h * 2)));
DB( g_print(" raw :: [%.2f - %.2f] range=%.2f\n", chart->rawmin, chart->rawmax, chart->range) );
DB( g_print(" raw :: maxticks=%d (%g / (%g*2))\n", maxticks, chart->graph_height, chart->font_h) );
DB( g_print("\n") );
chart->unit = CalculateStepSize((hibound-lobound), maxticks);
chart->min = -chart->unit * ceil(-lobound/chart->unit);
chart->max = chart->unit * ceil(hibound/chart->unit);
chart->range = chart->max - chart->min;
chart->div = chart->range / chart->unit;
DB( g_print(" end :: interval=%.2f, ticks=%d\n", chart->unit, chart->div) );
DB( g_print(" end :: [%.2f - %.2f], range=%.2f\n", chart->min, chart->max, chart->range) );
}
static void chart_calculation(GtkChart *chart)
{
GtkWidget *drawarea = chart->drawarea;
GdkWindow *gdkwindow;
cairo_surface_t *surf = NULL;
cairo_t *cr;
int tw, th;
GtkAllocation allocation;
PangoLayout *layout;
gchar *valstr;
gint i;
DB( g_print("\n[gtkchart] calculation\n") );
gtk_widget_get_allocation(drawarea, &allocation);
chart->l = CHART_MARGIN;
chart->t = CHART_MARGIN;
chart->r = allocation.width - CHART_MARGIN;
chart->b = allocation.height - CHART_MARGIN;
chart->w = allocation.width - (CHART_MARGIN*2);
chart->h = allocation.height - (CHART_MARGIN*2);
gdkwindow = gtk_widget_get_window(chart->drawarea);
if(!gdkwindow)
{
surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
cr = cairo_create (surf);
}
else
cr = gdk_cairo_create (gdkwindow);
/* Create a PangoLayout, set the font and text */
layout = pango_cairo_create_layout (cr);
// compute title
chart->title_zh = 0;
if(chart->title != NULL)
{
chart_set_font_size(chart, CHART_FONT_SIZE_TITLE);
pango_layout_set_font_description (layout, chart->pfd);
pango_layout_set_text (layout, chart->title, -1);
pango_layout_get_size (layout, &tw, &th);
chart->title_zh = (th / PANGO_SCALE);
DB( g_print(" - title: %s w=%d h=%d\n", chart->title, tw, th) );
}
// compute subtitle
chart->subtitle_zh = 0;
if(chart->subtitle != NULL)
{
chart_set_font_size(chart, CHART_FONT_SIZE_SUBTITLE);
pango_layout_set_font_description (layout, chart->pfd);
pango_layout_set_text (layout, chart->subtitle, -1);
pango_layout_get_size (layout, &tw, &th);
chart->subtitle_zh = (th / PANGO_SCALE);
DB( g_print(" - title: %s w=%d h=%d\n", chart->subtitle, tw, th) );
}
chart->subtitle_y = chart->t + chart->title_zh;
// todo: compute maxwidth of item labels
double label_w = 0;
for(i=0;inb_items;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
// category width
pango_layout_set_text (layout, item->label, -1);
pango_layout_get_size (layout, &tw, &th);
label_w = MAX(label_w, (tw / PANGO_SCALE));
}
chart->label_w = label_w + CHART_SPACING;
DB( g_print(" - label_w:%g\n", chart->label_w) );
// compute other text
chart_set_font_size(chart, CHART_FONT_SIZE_NORMAL);
pango_layout_set_font_description (layout, chart->pfd);
// compute amount scale
valstr = chart_print_int(chart, (gint)chart->min);
pango_layout_set_text (layout, valstr, -1);
pango_layout_get_size (layout, &tw, &th);
chart->scale_w = (tw / PANGO_SCALE);
valstr = chart_print_int(chart, (gint)chart->max);
pango_layout_set_text (layout, valstr, -1);
pango_layout_get_size (layout, &tw, &th);
chart->scale_w = MAX(chart->scale_w, (tw / PANGO_SCALE));
DB( g_print(" - scale: %g,%g %g,%g\n", chart->l, 0.0, chart->scale_w, 0.0) );
// compute font height
chart->font_h = (th / PANGO_SCALE);
// compute graph region
switch(chart->type)
{
case CHART_TYPE_LINE:
case CHART_TYPE_COL:
chart->graph_x = chart->l + chart->scale_w + 2;
chart->graph_y = chart->t + chart->title_zh + chart->subtitle_zh;
chart->graph_width = chart->w - chart->scale_w - 2;
chart->graph_height = chart->h - chart->title_zh - chart->subtitle_zh;
break;
case CHART_TYPE_PIE:
chart->graph_x = chart->l;
chart->graph_y = chart->t + chart->title_zh + chart->subtitle_zh;
chart->graph_width = chart->w;
chart->graph_height = chart->h - chart->title_zh - chart->subtitle_zh;
break;
}
if(chart->title_zh > 0 || chart->subtitle_zh > 0)
{
chart->graph_y += CHART_MARGIN;
chart->graph_height -= CHART_MARGIN;
}
if(chart->type != CHART_TYPE_PIE && chart->show_xval)
chart->graph_height -= (chart->font_h + CHART_SPACING);
g_object_unref (layout);
cairo_destroy(cr);
cairo_surface_destroy(surf);
}
static void colchart_calculation(GtkChart *chart)
{
gint blkw;
DB( g_print("\n[column] calculation\n") );
//if expand : we compute available space
//blkw = floor(MAX(8, (chart->graph_width)/chart->nb_items));
// if fixed
blkw = chart->barw + 3;
if( chart->dual )
blkw = (chart->barw * 2) + 3;
chart->blkw = blkw;
chart->visible = chart->graph_width / blkw;
chart->visible = MIN(chart->visible, chart->nb_items);
chart->ox = chart->l;
chart->oy = chart->b;
if(chart->range > 0)
chart->oy = floor(chart->graph_y + (chart->max/chart->range) * chart->graph_height);
DB( g_print(" + ox=%f oy=%f\n", chart->ox, chart->oy) );
/* todo: hack on every xval */
if(chart->label_w > 0)
{
blkw = floor(MIN(chart->nb_items*chart->blkw, chart->graph_width) / chart->label_w);
if(blkw > 0 )
blkw = chart->visible / blkw;
chart->every_xval = MAX(1,blkw);
}
//chart->every_xval = chart->visible - floor(chart->graph_width / chart->label_w);
DB( g_print("vis=%d, width=%g, lbl_w=%g :: %d\n", chart->visible, chart->graph_width, chart->label_w, blkw) );
}
/*
** draw the scale
*/
static void colchart_draw_scale(GtkWidget *widget, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
double x, y;
gdouble curxval;
gint i, first;
DB( g_print("\n[column] draw scale\n") );
cairo_t *cr;
//gdkwindow = gtk_widget_get_window(widget);
//cr = gdk_cairo_create (gdkwindow);
//cr = gdk_cairo_create (widget->window);
cr = cairo_create (chart->surface);
cairo_set_line_width(cr, 1);
/* clip */
//cairo_rectangle(cr, CHART_MARGIN, 0, chart->w, chart->h + CHART_MARGIN);
//cairo_clip(cr);
/* draw vertical lines + legend */
if(chart->show_xval)
{
x = chart->graph_x + 1.5 + (chart->barw/2);
y = chart->oy;
first = gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
cairo_set_dash(cr, dashed3, 1, 0);
for(i=first; i<(first+chart->visible) ;i++)
{
if( !(i % chart->every_xval) )
{
//cairo_user_set_rgbcol(cr, &global_colors[GREY1]);
cairo_user_set_rgbacol(cr, &global_colors[THTEXT], 0.1);
//cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); //blue
cairo_move_to(cr, x, chart->graph_y);
cairo_line_to(cr, x, chart->b - chart->font_h);
cairo_stroke(cr);
}
x += chart->blkw;
}
}
/* horizontal lines */
curxval = chart->max;
cairo_set_dash(cr, 0, 0, 0);
for(i=0;i<=chart->div;i++)
{
//if(i == 0 || i == chart->div) /* top/bottom line */
//{
//cairo_set_dash(cr, 0, 0, 0);
//cairo_user_set_rgbcol(cr, &global_colors[GREY1]);
cairo_user_set_rgbacol(cr, &global_colors[THTEXT], 0.1);
//}
//else /* intermediate line (dotted) */
//{
//cairo_set_dash(cr, dashed3, 1, 0);
//cairo_user_set_rgbcol(cr, &global_colors[GREY1]);
//}
/* x axis ? */
if( curxval == 0.0 )
{
//cairo_set_dash(cr, 0, 0, 0);
cairo_user_set_rgbacol(cr, &global_colors[THTEXT], 0.8);
}
y = 0.5 + floor(chart->graph_y + ((i * chart->unit) / chart->range) * chart->graph_height);
DB( g_print(" + i=%d :: y=%f (%f / %f) * %f\n", i, y, i*chart->unit, chart->range, chart->graph_height) );
cairo_move_to(cr, chart->graph_x, y);
cairo_line_to(cr, chart->graph_x + chart->graph_width, y);
cairo_stroke(cr);
curxval -= chart->unit;
}
cairo_destroy(cr);
}
static void colchart_draw_scale_text(GtkWidget *widget, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
double x, y;
gdouble curxval;
gchar *valstr;
gint i, first;
cairo_t *cr;
PangoLayout *layout;
int tw, th;
DB( g_print("\n([column] draw scale text\n") );
//GdkWindow *gdkwindow;
//gdkwindow = gtk_widget_get_window(widget);
//cr = gdk_cairo_create (gdkwindow);
//cr = gdk_cairo_create (widget->window);
cr = cairo_create (chart->surface);
layout = pango_cairo_create_layout (cr);
cairo_set_line_width(cr, 1);
/* clip */
//cairo_rectangle(cr, CHART_MARGIN, 0, chart->w, chart->h + CHART_MARGIN);
//cairo_clip(cr);
//cairo_set_operator(cr, CAIRO_OPERATOR_SATURATE);
chart_set_font_size(chart, CHART_FONT_SIZE_NORMAL);
pango_layout_set_font_description (layout, chart->pfd);
/* draw x-legend (items) */
if(chart->show_xval)
{
x = chart->graph_x + 1.5 + (chart->barw/2);
y = chart->b - chart->font_h;
first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
for(i=first; i<(first+chart->visible) ;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
if( !(i % chart->every_xval) )
{
valstr = item->label;
pango_layout_set_text (layout, valstr, -1);
pango_layout_get_size (layout, &tw, &th);
DB( g_print("%s w=%d h=%d\n", valstr, tw, th) );
cairo_user_set_rgbacol(cr, &global_colors[THTEXT], 0.78);
cairo_move_to(cr, x - ((tw / PANGO_SCALE)/2), y);
//cairo_move_to(cr, x, y);
pango_cairo_show_layout (cr, layout);
/*cairo_user_set_rgbcol(cr, &global_colors[TEXT]);
cairo_move_to(cr, x, y);
cairo_line_to(cr, x, y + te.height);
cairo_stroke(cr);*/
}
x += chart->blkw;
}
}
/* draw y-legend (amount) */
curxval = chart->max;
for(i=0;i<=chart->div;i++)
{
y = 0.5 + floor(chart->graph_y + ((i * chart->unit) / chart->range) * chart->graph_height);
DB( g_print(" + i=%d :: y=%f (%f / %f) * %f\n", i, y, i*chart->unit, chart->range, chart->graph_height) );
if( curxval != 0.0 )
{
valstr = chart_print_int(chart, (gint)curxval);
pango_layout_set_text (layout, valstr, -1);
pango_layout_get_size (layout, &tw, &th);
//DB( g_print("'%s', %f %f %f %f %f %f\n", valstr, te.x_bearing, te.y_bearing, te.width, te.height, te.x_advance, te.y_advance) );
// draw texts
cairo_move_to(cr, chart->graph_x - (tw / PANGO_SCALE) - 2, y - ((th / PANGO_SCALE)*0.8) );
cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78);
pango_cairo_show_layout (cr, layout);
}
curxval -= chart->unit;
}
g_object_unref (layout);
cairo_destroy(cr);
}
/*
** draw all visible bars
*/
static void colchart_draw_bars(GtkWidget *widget, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
cairo_t *cr;
double x, x2, y2, h;
gint i, first;
DB( g_print("\n[column] draw bars\n") );
x = chart->graph_x;
first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
cr = gdk_cairo_create (gtk_widget_get_window(widget));
//cr = cairo_create (chart->surface);
#if HELPDRAW == 1
x2 = x + 0.5;
cairo_set_line_width(cr, 1.0);
cairo_set_source_rgb(cr, 1.0, 0.0, 1.0); // violet
for(i=first; i<=(first+chart->visible) ;i++)
{
cairo_move_to(cr, x2, chart->graph_y);
cairo_line_to(cr, x2, chart->graph_x + chart->graph_height);
x2 += chart->blkw;
}
cairo_stroke(cr);
#endif
for(i=first; i<(first+chart->visible) ;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
gint color;
gint barw = chart->barw;
//if(!chart->datas1[i]) goto nextbar;
if(!chart->show_mono)
color = i % chart->color_scheme.nb_cols;
else
color = chart->color_scheme.cs_green;
cairo_user_set_rgbcol_over(cr, &chart->color_scheme.colors[color], i == chart->active);
if(item->serie1)
{
x2 = x;
h = floor((item->serie1 / chart->range) * chart->graph_height);
y2 = chart->oy - h;
if(item->serie1 < 0.0)
{
y2 += 1;
if(chart->show_mono)
{
color = chart->color_scheme.cs_red;
cairo_user_set_rgbcol_over(cr, &chart->color_scheme.colors[color], i == chart->active);
}
}
//DB( g_print(" + i=%d :: y2=%f h=%f (%f / %f) * %f\n", i, y2, h, chart->datas1[i], chart->range, chart->graph_height ) );
cairo_rectangle(cr, x2+2, y2, barw, h);
cairo_fill(cr);
}
if( chart->dual && item->serie2)
{
x2 = x + barw + 1;
h = floor((item->serie2 / chart->range) * chart->graph_height);
y2 = chart->oy - h;
cairo_rectangle(cr, x2+2, y2, barw, h);
cairo_fill(cr);
}
x += chart->blkw;
//debug
//gdk_draw_line (widget->window, widget->style->fg_gc[widget->state], x, chart->oy-chart->posbarh, x, chart->oy+chart->negbarh);
}
cairo_destroy(cr);
}
/*
** get the bar under the mouse pointer
*/
static gint colchart_get_active(GtkWidget *widget, gint x, gint y, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
gint retval;
gint index, first, px;
retval = -1;
if( x <= chart->r && x >= chart->graph_x && y >= chart->graph_y && y <= chart->b )
{
px = (x - chart->graph_x);
//py = (y - chart->oy);
first = gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
index = first + (px / chart->blkw);
if(index < chart->nb_items)
retval = index;
}
return(retval);
}
static void colchart_first_changed( GtkAdjustment *adj, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
//gint first;
DB( g_print("\n[column] first changed\n") );
//first = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj));
//DB( g_print(" first=%d\n", first) );
/*
DB( g_print("scrollbar\n adj=%8x, low=%.2f upp=%.2f val=%.2f step=%.2f page=%.2f size=%.2f\n", adj,
adj->lower, adj->upper, adj->value, adj->step_increment, adj->page_increment, adj->page_size) );
*/
/* Set the number of decimal places to which adj->value is rounded */
//gtk_scale_set_digits (GTK_SCALE (hscale), (gint) adj->value);
//gtk_scale_set_digits (GTK_SCALE (vscale), (gint) adj->value);
drawarea_full_redraw (chart->drawarea, chart);
gtk_widget_queue_draw(chart->drawarea);
}
/*
** scrollbar set values for upper, page size, and also show/hide
*/
static void colchart_scrollbar_setvalues(GtkChart *chart)
{
GtkAdjustment *adj = chart->adjustment;
gint first;
g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
DB( g_print("\n[column] sb_set_values\n") );
first = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj));
DB( g_print(" entries=%d, visible=%d\n", chart->nb_items, chart->visible) );
DB( g_print(" first=%d, upper=%d, pagesize=%d\n", first, chart->nb_items, chart->visible) );
gtk_adjustment_set_upper(adj, (gdouble)chart->nb_items);
gtk_adjustment_set_page_size(adj, (gdouble)chart->visible);
gtk_adjustment_set_page_increment(adj, (gdouble)chart->visible);
if(first+chart->visible > chart->nb_items)
{
gtk_adjustment_set_value(adj, (gdouble)chart->nb_items - chart->visible);
}
gtk_adjustment_changed (adj);
if( chart->visible < chart->nb_items )
gtk_widget_hide(GTK_WIDGET(chart->scrollbar));
else
gtk_widget_show(GTK_WIDGET(chart->scrollbar));
}
/* line section */
/*
** draw all visible lines
*/
static void linechart_draw_plot(cairo_t *cr, double x, double y, double r, GtkChart *chart)
{
cairo_set_line_width(cr, r / 2);
cairo_user_set_rgbcol(cr, &global_colors[THBASE]);
cairo_arc(cr, x, y, r, 0, 2*M_PI);
cairo_stroke_preserve(cr);
//cairo_set_source_rgb(cr, COLTOCAIRO(0), COLTOCAIRO(119), COLTOCAIRO(204));
cairo_user_set_rgbcol(cr, &chart->color_scheme.colors[chart->color_scheme.cs_blue]);
cairo_fill(cr);
}
static void linechart_draw_lines(GtkWidget *widget, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
cairo_t *cr;
double x, y, x2, y2, firstx, lastx, linew;
gint first, i;
DB( g_print("\n[line] draw lines\n") );
x = chart->graph_x;
y = chart->oy;
first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
cr = gdk_cairo_create (gtk_widget_get_window(widget));
//cr = cairo_create (chart->surface);
/* clip */
//cairo_rectangle(cr, CHART_MARGIN, 0, chart->w, chart->h + CHART_MARGIN);
//cairo_clip(cr);
#if HELPDRAW == 1
x2 = x + 0.5;
cairo_set_line_width(cr, 1.0);
cairo_set_source_rgb(cr, 1.0, 0.0, 1.0); // violet
for(i=first; i<=(first+chart->visible) ;i++)
{
cairo_move_to(cr, x2, chart->graph_y);
cairo_line_to(cr, x2, chart->graph_x + chart->graph_height);
x2 += chart->blkw;
}
cairo_stroke(cr);
#endif
//todo: it should be possible to draw line & plot together using surface and composite fill, or sub path ??
lastx = x;
firstx = x;
linew = 4.0;
if(chart->barw < 24)
{
linew = 1 + (chart->barw / 8.0);
}
cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
cairo_set_line_width(cr, linew);
for(i=first; i<(first+chart->visible) ;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
x2 = x + (chart->blkw)/2;
y2 = chart->oy - (item->serie1 / chart->range) * chart->graph_height;
if( i == first)
{
firstx = x2;
cairo_move_to(cr, x2, y2);
}
else
{
if( i < (chart->nb_items) )
{
cairo_line_to(cr, x2, y2);
lastx = x2;
}
else
lastx = x2 - chart->barw;
}
x += chart->blkw;
}
cairo_user_set_rgbcol(cr, &chart->color_scheme.colors[chart->color_scheme.cs_blue]);
cairo_stroke_preserve(cr);
cairo_line_to(cr, lastx, y);
cairo_line_to(cr, firstx, y);
cairo_close_path(cr);
cairo_user_set_rgbacol(cr, &chart->color_scheme.colors[chart->color_scheme.cs_blue], AREA_ALPHA);
cairo_fill(cr);
x = chart->graph_x;
y = chart->oy;
first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
// draw plots
for(i=first; i<(first+chart->visible) ;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
x2 = x + (chart->blkw)/2;
y2 = chart->oy - (item->serie1 / chart->range) * chart->graph_height;
linechart_draw_plot(cr, x2, y2, i == chart->active ? linew+1 : linew, chart);
x += chart->blkw;
}
/* overdrawn */
DB( g_print(" min=%.2f range=%.2f\n", chart->min, chart->range) );
if( chart->show_over )
{
if(chart->minimum != 0 && chart->minimum >= chart->min)
{
y = 0.5 + chart->oy + (ABS(chart->minimum)/chart->range) * chart->graph_height;
y2 = (ABS(chart->min)/chart->range) * chart->graph_height - (y - chart->oy) + 1;
cairo_set_source_rgba(cr, COLTOCAIRO(255), COLTOCAIRO(0), COLTOCAIRO(0), AREA_ALPHA / 2);
DB( g_print(" draw over: x%f, y%f, w%f, h%f\n", chart->l, y, chart->w, y2) );
cairo_rectangle(cr, chart->graph_x, y, chart->graph_width, y2 );
cairo_fill(cr);
cairo_set_line_width(cr, 1.0);
cairo_set_source_rgb(cr, COLTOCAIRO(255), COLTOCAIRO(0), COLTOCAIRO(0));
cairo_set_dash (cr, dashed3, 1, 0);
cairo_move_to(cr, chart->graph_x, y);
cairo_line_to (cr, chart->graph_x + chart->graph_width, y);
cairo_stroke(cr);
}
}
cairo_destroy(cr);
}
/*
** get the point under the mouse pointer
*/
static gint linechart_get_active(GtkWidget *widget, gint x, gint y, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
gint retval;
gint first, index, px;
DB( g_print("\n[line] get active\n") );
retval = -1;
if( x <= chart->r && x >= chart->graph_x && y >= chart->graph_y && y <= chart->b )
{
px = (x - chart->graph_x);
//py = (y - chart->oy);
first = gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
index = first + (px / (chart->blkw));
if(index < chart->nb_items)
retval = index;
}
return(retval);
}
/* pie section */
static void piechart_calculation(GtkChart *chart)
{
GtkWidget *drawarea = chart->drawarea;
GtkAllocation allocation;
gint w, h;
DB( g_print("\n[pie] calculation\n") );
w = chart->graph_width;
h = chart->graph_height;
chart->rayon = MIN(w, h);
gtk_widget_get_allocation(drawarea, &allocation);
chart->ox = chart->graph_x + (chart->graph_width / 2);
chart->oy = chart->graph_y + (chart->rayon / 2);
}
static void piechart_draw_slices(GtkWidget *widget, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
cairo_t *cr;
if(chart->nb_items <= 0)
return;
DB( g_print("\n[pie] draw slices\n") );
//cairo drawing
double a1 = 0 * (M_PI / 180);
double a2 = 360 * (M_PI / 180);
//g_print("angle1=%.2f angle2=%.2f\n", a1, a2);
double cx = chart->ox;
double cy = chart->oy;
double radius = chart->rayon/2;
gint i;
double dx, dy;
double sum = 0.0;
gint color;
cr = gdk_cairo_create (gtk_widget_get_window(widget));
//cr = cairo_create (chart->surface);
for(i=0; i< chart->nb_items ;i++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, i);
a1 = ((360 * (sum / chart->total)) - 90) * (M_PI / 180);
sum += ABS(item->serie1);
a2 = ((360 * (sum / chart->total)) - 90) * (M_PI / 180);
if(i < chart->nb_items-1) a2 += 0.0175;
dx = cx;
dy = cy;
cairo_move_to(cr, dx, dy);
cairo_arc(cr, dx, dy, radius, a1, a2);
#if PIE_LINE_SLICE == 1
cairo_set_line_width(cr, 1.0);
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_line_to(cr, cx, cy);
cairo_stroke_preserve(cr);
#endif
DB( g_print("%d: %.2f%% %.2f %.2f\n", i, sum / chart->total, a1, a2) );
//g_print("color : %f %f %f\n", COLTOCAIRO(colors[i].r), COLTOCAIRO(colors[i].g), COLTOCAIRO(colors[i].b));
color = i % chart->color_scheme.nb_cols;
cairo_user_set_rgbcol_over(cr, &chart->color_scheme.colors[color], i == chart->active);
cairo_fill(cr);
}
#if SOFT_LIGHT == 1
cairo_pattern_t *pat1;
a1 = 0;
a2 = 2 * M_PI;
pat1 = cairo_pattern_create_radial( cx, cy, 0, cx, cy, radius);
cairo_pattern_add_color_stop_rgba(pat1, 0.0, 1.0, 1.0, 1.0, .50);
cairo_pattern_add_color_stop_rgba(pat1, 0.9, 1.0, 1.0, 1.0, 0.1);
cairo_arc(cr, cx, cy, radius, a1, a2);
cairo_set_source(cr, pat1);
cairo_fill(cr);
#endif
#if GRADIENT == 1
cairo_pattern_t *pat1;
a1 = 0;
a2 = 2 * M_PI;
double gradius = radius - 8;
// start point, end point
pat1 = cairo_pattern_create_linear(cx, cy-gradius, cx, cy+gradius);
cairo_pattern_add_color_stop_rgba(pat1, 0.0, 1.0, 1.0, 1.0, .15);
cairo_pattern_add_color_stop_rgba(pat1, 1.0, 1.0, 1.0, 1.0, 0.0);
//debug
//cairo_rectangle(cr, cx-radius, cy-radius, radius*2, radius*2);
cairo_arc(cr, cx, cy, gradius, a1, a2);
cairo_set_source(cr, pat1);
cairo_fill(cr);
#endif
#if CHART_PIE_DONUT == 1
a1 = 0;
a2 = 2 * M_PI;
//original
//radius = (gint)((chart->rayon/3) * (1 / PHI));
//5.1
//radius = (gint)((chart->rayon/2) * 2 / 3);
//ynab
radius = (gint)(chart->rayon/2) * 0.5;
cairo_arc(cr, cx, cy, radius, a1, a2);
cairo_user_set_rgbcol(cr, &global_colors[THBASE]);
cairo_fill(cr);
#endif
cairo_destroy(cr);
}
static gint piechart_get_active(GtkWidget *widget, gint x, gint y, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
gint retval, px, py;
gint index;
double h;
DB( g_print("\n[pie] get active\n") );
px = x - chart->ox;
py = y - chart->oy;
h = sqrt( pow(px,2) + pow(py,2) );
retval = -1;
if(h < (chart->rayon/2))
{
double angle, b;
b = (acos(px / h) * 180) / M_PI;
angle = py > 0 ? b : 360 - b;
angle += 90;
if(angle > 360) angle -= 360;
//angle = 360 - angle;
//todo optimize
gdouble cumul = 0;
for(index=0; index< chart->nb_items ;index++)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, index);
cumul += ABS(item->serie1/chart->total)*360;
if( cumul > angle )
{
retval = index;
break;
}
}
//DB( g_print(" inside: x=%d, y=%d\n", x, y) );
//DB( g_print(" inside: b=%f angle=%f, slice is %d\n", b, angle, index) );
}
return(retval);
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
static gboolean drawarea_full_redraw(GtkWidget *widget, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
cairo_t *cr;
PangoLayout *layout;
int tw, th;
DB( g_print("\n[gtkchart] drawarea full redraw\n") );
cr = cairo_create (chart->surface);
/* fillin the back in white */
//cairo_user_set_rgbcol(cr, &global_colors[WHITE]);
cairo_user_set_rgbcol(cr, &global_colors[THBASE]);
cairo_paint(cr);
if(chart->nb_items == 0)
{
cairo_destroy(cr);
return FALSE;
}
/*debug help draws */
#if HELPDRAW == 1
cairo_set_line_width(cr, 1.0);
cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); //green
cairo_rectangle(cr, chart->l+0.5, chart->t+0.5, chart->w, chart->h);
cairo_stroke(cr);
cairo_set_source_rgb(cr, 1.0, 0.5, 0.0); //orange
cairo_rectangle(cr, chart->graph_x+0.5, chart->graph_y+0.5, chart->graph_width, chart->graph_height);
cairo_stroke(cr);
#endif
// draw title
if(chart->title)
{
layout = pango_cairo_create_layout (cr);
chart_set_font_size(chart, CHART_FONT_SIZE_TITLE);
pango_layout_set_font_description (layout, chart->pfd);
pango_layout_set_text (layout, chart->title, -1);
pango_layout_get_size (layout, &tw, &th);
cairo_user_set_rgbcol(cr, &global_colors[THTEXT]);
cairo_move_to(cr, chart->l, chart->t);
pango_cairo_show_layout (cr, layout);
#if HELPDRAW == 1
double dashlength;
cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); //blue
dashlength = 3;
cairo_set_dash (cr, &dashlength, 1, 0);
cairo_move_to(cr, chart->l, chart->t);
cairo_rectangle(cr, chart->l, chart->t, (tw / PANGO_SCALE), (th / PANGO_SCALE));
cairo_stroke(cr);
#endif
g_object_unref (layout);
}
switch(chart->type)
{
case CHART_TYPE_COL:
colchart_draw_scale(widget, chart);
//colchart_draw_bars(widget, chart);
colchart_draw_scale_text(widget, chart);
break;
case CHART_TYPE_LINE:
colchart_draw_scale(widget, chart);
//linechart_draw_lines(widget, chart);
colchart_draw_scale_text(widget, chart);
break;
case CHART_TYPE_PIE:
//piechart_draw_slices(widget, chart);
break;
}
cairo_destroy(cr);
return TRUE;
}
static gboolean
drawarea_configure_event (GtkWidget *widget,
GdkEventConfigure *event,
gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
GtkAllocation allocation;
GtkStyleContext *context;
PangoFontDescription *desc;
gboolean colfound;
GdkRGBA color;
DB( g_print("\n[gtkchart] drawarea configure \n") );
gtk_widget_get_allocation (widget, &allocation);
DB( g_print("w=%d h=%d\n", allocation.width, allocation.height) );
if (chart->surface)
cairo_surface_destroy (chart->surface);
chart->surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
CAIRO_CONTENT_COLOR,
allocation.width,
allocation.height);
context = gtk_widget_get_style_context (widget);
chart_color_global_default();
// get base color
colfound = gtk_style_context_lookup_color(context, "theme_base_color", &color);
if(!colfound)
colfound = gtk_style_context_lookup_color(context, "base_color", &color);
if( colfound )
{
struct rgbcol *tcol = &global_colors[THBASE];
tcol->r = color.red * 255;
tcol->g = color.green * 255;
tcol->b = color.blue * 255;
DB( g_print(" - theme base col: %x %x %x\n", tcol->r, tcol->g, tcol->b) );
}
//get text color
colfound = gtk_style_context_lookup_color(context, "theme_fg_color", &color);
if(!colfound)
gtk_style_context_lookup_color(context, "fg_color", &color);
if( colfound )
{
struct rgbcol *tcol = &global_colors[THTEXT];
tcol->r = color.red * 255;
tcol->g = color.green * 255;
tcol->b = color.blue * 255;
DB( g_print(" - theme text (bg) col: %x %x %x\n", tcol->r, tcol->g, tcol->b) );
}
/* get and copy the font */
gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "font", &desc, NULL);
if(chart->pfd)
{
pango_font_description_free (chart->pfd);
chart->pfd = NULL;
}
chart->pfd = pango_font_description_copy(desc);
chart->pfd_size = pango_font_description_get_size (desc) / PANGO_SCALE;
//chart->barw = (6 + chart->pfd_size) * PHI;
DB( g_print("family: %s\n", pango_font_description_get_family(chart->pfd) ) );
DB( g_print("size : %d (%d)\n", chart->pfd_size, chart->pfd_size/PANGO_SCALE ) );
DB( g_print("isabs : %d\n", pango_font_description_get_size_is_absolute (chart->pfd) ) );
if( gtk_widget_get_realized(widget))
{
chart_recompute(chart);
drawarea_full_redraw(widget, chart);
}
/* We've handled the configure event, no need for further processing. */
return TRUE;
}
static void drawarea_realize_callback(GtkWidget *widget, gpointer user_data)
{
//GtkChart *chart = GTK_CHART(user_data);
DB( g_print("\n[gtkchart] drawarea realize\n") );
//chart_recompute(chart);
}
static gboolean drawarea_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
if( !gtk_widget_get_realized(widget) || chart->surface == NULL )
return FALSE;
DB( g_print("\n[gtkchart] drawarea draw callback\n") );
cairo_set_source_surface (cr, chart->surface, 0, 0);
cairo_paint (cr);
switch(chart->type)
{
case CHART_TYPE_COL:
colchart_draw_bars(widget, chart);
break;
case CHART_TYPE_LINE:
linechart_draw_lines(widget, chart);
break;
case CHART_TYPE_PIE:
piechart_draw_slices(widget, chart);
break;
}
return FALSE;
}
static gboolean drawarea_querytooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
gchar *strval, *strval2, *buffer;
gboolean retval = FALSE;
if(chart->surface == NULL)
return FALSE;
DB( g_print("\n[gtkchart] drawarea querytooltip\n") );
DB( g_print(" x=%d, y=%d kbm=%d\n", x, y, keyboard_mode) );
if(chart->lastactive != chart->active)
{
goto end;
}
if(chart->active >= 0)
{
ChartItem *item = &g_array_index(chart->items, ChartItem, chart->active);
strval = chart_print_double(chart, chart->buffer1, item->serie1);
if( !chart->dual )
{
//#1420495 don't use g_markup_printf_escaped
if( chart->type == CHART_TYPE_PIE )
buffer = g_strdup_printf("%s\n%s\n%.2f%%", item->label, strval, item->rate);
else
buffer = g_strdup_printf("%s\n%s", item->label, strval);
}
else
{
strval2 = chart_print_double(chart, chart->buffer2, item->serie2);
buffer = g_strdup_printf("%s\n+%s\n%s", item->label, strval2, strval);
}
gtk_tooltip_set_text(tooltip, buffer);
//gtk_label_set_markup(GTK_LABEL(chart->ttlabel), buffer);
g_free(buffer);
retval = TRUE;
}
end:
chart->lastactive = chart->active;
return retval;
}
static gboolean drawarea_motionnotifyevent_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
{
GtkChart *chart = GTK_CHART(user_data);
gboolean retval = TRUE;
gint x, y;
if(chart->surface == NULL || chart->nb_items == 0)
return FALSE;
DB( g_print("\n[gtkchart] drawarea motion\n") );
x = event->x;
y = event->y;
//todo see this
if(event->is_hint)
{
DB( g_print(" is hint\n") );
gdk_window_get_device_position(event->window, event->device, &x, &y, NULL);
//gdk_window_get_pointer(event->window, &x, &y, NULL);
//return FALSE;
}
switch(chart->type)
{
case CHART_TYPE_COL:
chart->active = colchart_get_active(widget, x, y, chart);
break;
case CHART_TYPE_LINE:
chart->active = linechart_get_active(widget, x, y, chart);
break;
case CHART_TYPE_PIE:
chart->active = piechart_get_active(widget, x, y, chart);
break;
}
// rollover redraw ?
DB( g_print(" active: last=%d, curr=%d\n", chart->lastactive, chart->active) );
if(chart->lastactive != chart->active)
{
GdkRectangle update_rect;
gint first;
DB( g_print(" motion rollover redraw :: active=%d\n", chart->active) );
first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment));
/* pie : always invalidate all graph area */
if( chart->type == CHART_TYPE_PIE )
{
update_rect.x = chart->graph_x;
update_rect.y = chart->graph_y;
update_rect.width = chart->graph_width;
update_rect.height = chart->graph_height;
/* Now invalidate the affected region of the drawing area. */
gdk_window_invalidate_rect (gtk_widget_get_window (widget),
&update_rect,
FALSE);
}
if(chart->lastactive != -1)
{
/* column/line : invalidate rollover */
if( chart->type != CHART_TYPE_PIE )
{
update_rect.x = chart->graph_x + (chart->lastactive - first) * chart->blkw;
update_rect.y = chart->graph_y - 6;
update_rect.width = chart->blkw;
update_rect.height = chart->graph_height + 12;
}
/* Now invalidate the affected region of the drawing area. */
gdk_window_invalidate_rect (gtk_widget_get_window (widget),
&update_rect,
FALSE);
}
/* column/line : invalidate current item */
if(chart->type != CHART_TYPE_PIE)
{
update_rect.x = chart->graph_x + (chart->active - first) * chart->blkw;
update_rect.y = chart->graph_y - 6;
update_rect.width = chart->blkw;
update_rect.height = chart->graph_height + 12;
/* Now invalidate the affected region of the drawing area. */
gdk_window_invalidate_rect (gtk_widget_get_window (widget),
&update_rect,
FALSE);
}
//gtk_widget_queue_draw( widget );
//retval = FALSE;
}
DB( g_print(" x=%d, y=%d, time=%d\n", x, y, event->time) );
DB( g_print(" trigger tooltip query\n") );
gtk_tooltip_trigger_tooltip_query(gtk_widget_get_display(chart->drawarea));
return retval;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* public functions */
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
void gtk_chart_queue_redraw(GtkChart *chart)
{
DB( g_print("\n[gtkchart] queue redraw\n") );
if( gtk_widget_get_realized(chart->drawarea) )
{
chart_recompute(chart);
drawarea_full_redraw(chart->drawarea, chart);
gtk_widget_queue_draw( chart->drawarea );
}
}
/*
** change the model and/or column
*/
void gtk_chart_set_datas(GtkChart *chart, GtkTreeModel *model, guint column, gchar *title, gchar *subtitle)
{
g_return_if_fail (GTK_IS_CHART (chart));
if( GTK_IS_TREE_MODEL(model) )
{
chart_setup_with_model(chart, model, column, column);
if(title != NULL)
chart->title = g_strdup(title);
if(subtitle != NULL)
chart->subtitle = g_strdup(subtitle);
gtk_chart_queue_redraw(chart);
}
else
{
chart_clear(chart);
if( GTK_IS_LIST_STORE(chart->legend) )
gtk_list_store_clear (GTK_LIST_STORE(chart->legend));
}
}
/*
** change the model and/or column
*/
void gtk_chart_set_dualdatas(GtkChart *chart, GtkTreeModel *model, guint column1, guint column2, gchar *title, gchar *subtitle)
{
g_return_if_fail (GTK_IS_CHART (chart));
if( GTK_IS_TREE_MODEL(model) )
{
chart_setup_with_model(chart, model, column1, column2 );
if(title != NULL)
chart->title = g_strdup(title);
if(subtitle != NULL)
chart->subtitle = g_strdup(subtitle);
gtk_chart_queue_redraw(chart);
}
else
{
chart_clear(chart);
if( GTK_IS_LIST_STORE(chart->legend) )
gtk_list_store_clear (GTK_LIST_STORE(chart->legend));
}
}
/*
** change the type dynamically
*/
void gtk_chart_set_type(GtkChart * chart, gint type)
{
g_return_if_fail (GTK_IS_CHART (chart));
//g_return_if_fail (type < CHART_TYPE_MAX);
DB( g_print("\n[gtkchart] set type %d\n", type) );
chart->type = type;
chart->dual = FALSE;
gtk_chart_queue_redraw(chart);
}
/* = = = = = = = = = = parameters = = = = = = = = = = */
void gtk_chart_set_color_scheme(GtkChart * chart, gint index)
{
colorscheme_init(&chart->color_scheme, index);
}
/*
** set the minor parameters
*/
void gtk_chart_set_minor_prefs(GtkChart * chart, gdouble rate, gchar *symbol)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->minor_rate = rate;
chart->minor_symbol = symbol;
}
void gtk_chart_set_absolute(GtkChart * chart, gboolean abs)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->abs = abs;
}
void gtk_chart_set_currency(GtkChart * chart, guint32 kcur)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->kcur = kcur;
}
/*
** set the overdrawn minimum
*/
void gtk_chart_set_overdrawn(GtkChart * chart, gdouble minimum)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->minimum = minimum;
//if(chart->type == CHART_TYPE_LINE)
// chart_recompute(chart);
}
/*
** set the every_xval
*/
void gtk_chart_set_every_xval(GtkChart * chart, gint gap)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->every_xval = gap;
//if(chart->type != CHART_TYPE_PIE)
// chart_recompute(chart);
}
/*
** set the barw
*/
void gtk_chart_set_barw(GtkChart * chart, gdouble barw)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->barw = barw;
if(chart->type != CHART_TYPE_PIE)
gtk_chart_queue_redraw(chart);
}
/*
** set the show mono (colors)
*/
void gtk_chart_set_showmono(GtkChart * chart, gboolean mono)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->show_mono = mono;
if(chart->type != CHART_TYPE_PIE)
gtk_chart_queue_redraw(chart);
}
/* = = = = = = = = = = visibility = = = = = = = = = = */
/*
** change the legend visibility
*/
void gtk_chart_show_legend(GtkChart * chart, gboolean visible, gboolean showextracol)
{
GtkTreeViewColumn *column;
g_return_if_fail (GTK_IS_CHART (chart));
if(visible == TRUE)
gtk_widget_show(chart->scrollwin);
else
gtk_widget_hide(chart->scrollwin);
/* manage column visibility */
column = gtk_tree_view_get_column (GTK_TREE_VIEW(chart->treeview), 1); //amount
gtk_tree_view_column_set_visible (column, showextracol);
column = gtk_tree_view_get_column (GTK_TREE_VIEW(chart->treeview), 2); //percent
gtk_tree_view_column_set_visible (column, showextracol);
}
/*
** change the x-value visibility
*/
void gtk_chart_show_xval(GtkChart * chart, gboolean visible)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->show_xval = visible;
//if(chart->type != CHART_TYPE_PIE)
// chart_recompute(chart);
}
/*
** chnage the overdrawn visibility
*/
void gtk_chart_show_overdrawn(GtkChart * chart, gboolean visible)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->show_over = visible;
//if(chart->type == CHART_TYPE_LINE)
// chart_recompute(chart);
}
/*
** change the minor visibility
*/
void gtk_chart_show_minor(GtkChart * chart, gboolean minor)
{
g_return_if_fail (GTK_IS_CHART (chart));
chart->minor = minor;
if(chart->type != CHART_TYPE_PIE)
gtk_chart_queue_redraw(chart);
gtk_tree_view_columns_autosize (GTK_TREE_VIEW(chart->treeview));
gtk_widget_queue_draw (chart->treeview);
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* legend list */
#define LEG_SQUARE_SIZE 12
static GdkPixbuf *create_color_pixbuf (struct rgbcol *color)
{
GdkPixbuf *pixbuf;
guint32 pixel;
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, LEG_SQUARE_SIZE, LEG_SQUARE_SIZE);
pixel = 0xFF;
pixel |= (color->r << 24);
pixel |= (color->g << 16);
pixel |= (color->b << 8);
/*g_print("%08x\n", (0xFF & col->red) << 24 );
g_print("%08x\n", (0xFF & col->green) << 16 );
g_print("%08x\n", (0xFF & col->blue) << 8 );
g_print("%x %x %x => %08x\n", col->red, col->green, col->blue, pixel);*/
gdk_pixbuf_fill(pixbuf, pixel);
return pixbuf;
}
static void legend_list_cell_data_function(GtkTreeViewColumn *col,
GtkCellRenderer *renderer,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
GdkPixbuf *pixbuf;
gchar *title;
gtk_tree_model_get(model, iter,
LST_LEGEND_COLOR, &pixbuf,
LST_LEGEND_TITLE, &title,
-1);
switch(GPOINTER_TO_INT(user_data))
{
case LST_LEGEND_COLOR:
g_object_set(renderer, "pixbuf", pixbuf, NULL);
break;
case LST_LEGEND_TITLE:
g_object_set(renderer, "text", title, NULL);
break;
}
}
static void
legend_list_float_cell_data_function (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
{
GtkChart *chart = user_data;
gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
gdouble amount;
gtk_tree_model_get(model, iter,
LST_LEGEND_AMOUNT, &amount,
-1);
hb_strfmon(buf, G_ASCII_DTOSTR_BUF_SIZE-1, amount, chart->kcur, chart->minor);
g_object_set(renderer,
"text", buf,
NULL);
}
static void legend_list_rate_cell_data_function (GtkTreeViewColumn *col,
GtkCellRenderer *renderer,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
gdouble rate;
gchar buf[8];
gtk_tree_model_get(model, iter,
LST_LEGEND_RATE, &rate,
-1);
g_snprintf(buf, sizeof(buf), "%.02f %%", rate);
g_object_set(renderer, "text", buf, NULL);
}
static GtkWidget *legend_list_new(GtkChart *chart)
{
GtkListStore *store;
GtkWidget *view;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
store = gtk_list_store_new(NUM_LST_LEGEND,
G_TYPE_POINTER,
GDK_TYPE_PIXBUF,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE
);
//treeview
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
g_object_unref(store);
#if MYDEBUG == 1
/* GtkStyle *style;
PangoFontDescription *font_desc;
g_print("legend_list_new font\n");
style = gtk_widget_get_style(GTK_WIDGET(view));
font_desc = style->font_desc;
g_print("family: %s\n", pango_font_description_get_family(font_desc) );
g_print("size: %d (%d)\n", pango_font_description_get_size (font_desc), pango_font_description_get_size (font_desc )/PANGO_SCALE );
*/
#endif
// change the font size to a smaller one
/*PangoFontDescription *font = pango_font_description_new();
pango_font_description_set_size (font, 8 * PANGO_SCALE);
gtk_widget_modify_font(GTK_WIDGET(view), font);
pango_font_description_free( font );*/
GtkStyleContext *context;
PangoFontDescription *desc, *nfd;
gint nfsize;
context = gtk_widget_get_style_context (chart->drawarea);
gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "font", &desc, NULL);
nfd = pango_font_description_copy(desc);
nfsize = pango_font_description_get_size (desc) / PANGO_SCALE;
pango_font_description_set_size(nfd, MAX(8, nfsize-2) * PANGO_SCALE);
gtk_widget_override_font(GTK_WIDGET(view), nfd);
pango_font_description_free (nfd);
// column 1
column = gtk_tree_view_column_new();
renderer = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start(column, renderer, FALSE);
gtk_tree_view_column_set_cell_data_func(column, renderer, legend_list_cell_data_function, GINT_TO_POINTER(LST_LEGEND_COLOR), NULL);
renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start(column, renderer, FALSE);
gtk_tree_view_column_set_cell_data_func(column, renderer, legend_list_cell_data_function, GINT_TO_POINTER(LST_LEGEND_TITLE), NULL);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
gtk_tree_view_append_column (GTK_TREE_VIEW(view), column);
// column 2
column = gtk_tree_view_column_new();
//gtk_tree_view_column_set_title(column, name);
renderer = gtk_cell_renderer_text_new ();
g_object_set(renderer, "xalign", 1.0, NULL);
gtk_tree_view_column_pack_start(column, renderer, FALSE);
gtk_tree_view_column_set_cell_data_func(column, renderer, legend_list_float_cell_data_function, chart, NULL);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
//gtk_tree_view_column_set_resizable(column, TRUE);
//gtk_tree_view_column_set_alignment (column, 0.5);
//gtk_tree_view_column_set_spacing( column, 16 );
gtk_tree_view_append_column (GTK_TREE_VIEW(view), column);
gtk_tree_view_column_set_visible (column, FALSE);
// column 3
column = gtk_tree_view_column_new();
//gtk_tree_view_column_set_title(column, "%");
renderer = gtk_cell_renderer_text_new ();
g_object_set(renderer, "xalign", 1.0, NULL);
gtk_tree_view_column_pack_start(column, renderer, TRUE);
//gtk_tree_view_column_add_attribute(column, renderer, "text", id);
gtk_tree_view_column_set_cell_data_func(column, renderer, legend_list_rate_cell_data_function, GINT_TO_POINTER(3), NULL);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
//gtk_tree_view_column_set_alignment (column, 0.5);
gtk_tree_view_append_column (GTK_TREE_VIEW(view), column);
gtk_tree_view_column_set_visible (column, FALSE);
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_NONE);
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(view), FALSE);
//gtk_tree_view_set_reorderable (GTK_TREE_VIEW(view), TRUE);
/*
GValue value = { 0, };
g_value_init (&value, G_TYPE_INT);
g_value_set_int (&value, 20);
g_object_set_property(view, "vertical-separator", &value);
g_value_unset (&value);
*/
return(view);
}