/*****************************************************************************/ /* Klavaro - a flexible touch typing tutor */ /* Copyright (C) 2005, 2006, 2007, 2008 Felipe Castro */ /* Copyright (C) 2009, 2010, 2011 Free Software Foundation */ /* */ /* This program is free software, licensed under the terms of the GNU */ /* General Public License as published by the Free Software Foundation, */ /* either version 3 of the License, or (at your option) any later version. */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see . */ /*****************************************************************************/ /* * Charts management */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "auxiliar.h" #include "main.h" #include "callbacks.h" #include "tutor.h" #include "basic.h" #include "accuracy.h" #include "plot.h" static struct { GtkWidget *databox; GtkWidget *table; struct { gfloat x[DATA_POINTS+1]; gfloat y[DATA_POINTS+1]; } data; GtkDataboxGraph *point_kernel; GtkDataboxGraph *point_frame; GtkDataboxGraph *line_kernel; GtkDataboxGraph *line_frame; GtkDataboxGraph *line_outter; struct { gfloat x[1]; gfloat y[1]; } mark; GtkDataboxGraph *point_marker; struct { gfloat x[2]; gfloat y[2]; } goal; GtkDataboxGraph *line_goal; GtkDataboxGraph *grid; struct { gfloat x[2]; gfloat y[2]; } lim; GtkDataboxGraph *limits; } plot; gfloat accur[DATA_POINTS+1]; gfloat velo[DATA_POINTS+1]; gfloat fluid[DATA_POINTS+1]; gfloat score[DATA_POINTS+1]; gchar date[DATA_POINTS+1][20]; gchar hour[DATA_POINTS+1][20]; gint nchars[DATA_POINTS+1]; gchar lesson[DATA_POINTS+1][299]; glong n_points; gint plot_type; /******************************************************************************* * Interface functions */ GtkWidget * plot_get_databox () { return plot.databox; } /******************************************************************************* * Private functions static void plot_clip_data (gint i) { if (accur[i] > 100) accur[i] = 100.1; if (accur[i] < 60) accur[i] = 59.9; if (velo[i] > 100) velo[i] = 100.1; if (velo[i] < 0) velo[i] = -0.1; if (fluid[i] > 100) fluid[i] = 100.1; if (fluid[i] < 0) fluid[i] = -0.1; if (score[i] < 0) score[i] = -0.1; } */ static void plot_error_frequencies () { gint i; GdkColor color, color2; GdkColor color_black; GtkDatabox *box; box = GTK_DATABOX (plot.databox); n_points = accur_n_get (); if (n_points < 1) return; accur_sort (); for (i = 0; i < DATA_POINTS; i++) { if (i < n_points) plot.data.y[i] = accur_wrong_get (i); else plot.data.y[i] = 0; } /* Format the chart */ plot.mark.x[0] = -7; plot.mark.y[0] = -7; plot.lim.x[0] = 0; plot.lim.x[1] = DATA_POINTS + 2; plot.lim.y[0] = 0; plot.lim.y[1] = 1.05 * accur_wrong_get (0); if (plot.lim.y[1] < 1) plot.lim.y[1] = 1.05; /* White background */ gdk_color_parse ("#ffffff", &color); gtk_widget_modify_bg (plot.databox, GTK_STATE_NORMAL, &color); gdk_color_parse ("#000000", &color_black); gdk_color_parse (PLOT_GREEN_2, &color); gdk_color_parse (PLOT_RED_2, &color2); /* Point limits */ plot.limits = gtk_databox_points_new (2, plot.lim.x, plot.lim.y, &color_black, 1); gtk_databox_graph_add (box, plot.limits); gtk_databox_auto_rescale (box, 0.0); /* Bar kernel */ plot.point_kernel = gtk_databox_bars_new (i, plot.data.x, plot.data.y, &color, 5); gtk_databox_graph_add (box, plot.point_kernel); /* Bar frame */ plot.point_frame = gtk_databox_bars_new (i, plot.data.x, plot.data.y, &color_black, 7); gtk_databox_graph_add (box, plot.point_frame); /* Data marker */ plot.point_marker = gtk_databox_points_new (1, plot.mark.x, plot.mark.y, &color_black, 7); gtk_databox_graph_add (box, plot.point_marker); /* Redraw the plot */ gtk_widget_show_all (plot.table); } /******************************************************************************* * Functions to manage the plottings on the progress window */ void plot_initialize () { static gboolean inited = FALSE; gint i; if (inited) return; inited = TRUE; /* Initialize X data */ for (i = 0; i < DATA_POINTS; i++) plot.data.x[i] = i + 1; /* Data Box */ gtk_databox_create_box_with_scrollbars_and_rulers (&plot.databox, &plot.table, FALSE, FALSE, FALSE, FALSE); gtk_container_add (GTK_CONTAINER (get_wg ("frame_stat")), plot.table); g_signal_connect (G_OBJECT (plot.databox), "motion_notify_event", G_CALLBACK (on_databox_hovered), NULL); plot_draw_chart (1); } /********************************************************************** * Plots the statistics */ void plot_draw_chart (gint field) { gint i; gint lesson_n; gchar *tmp_locale; gchar *tmp_name; gchar tmp_str[2000]; FILE *fh; GdkColor color, color2, color3; GdkColor color_black; GtkDatabox *box; box = GTK_DATABOX (plot.databox); /* Blank the chart */ n_points = 0; gtk_databox_graph_remove_all (box); gtk_widget_hide (plot.table); plot_type = field; /* Error frequencies */ if (field == 6) { plot_error_frequencies (); return; } /* Auxiliar variable to track the lesson to be plot */ lesson_n = gtk_spin_button_get_value (GTK_SPIN_BUTTON (get_wg ("spinbutton_stat_lesson"))); if (tutor_get_type () == TT_BASIC) { gtk_widget_show (get_wg ("label_stat_lesson")); gtk_widget_show (get_wg ("spinbutton_stat_lesson")); } else { gtk_widget_hide (get_wg ("label_stat_lesson")); gtk_widget_hide (get_wg ("spinbutton_stat_lesson")); } /* Get the file name */ if (field < 4) tmp_name = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S "stat_", tutor_get_type_name (), ".txt", NULL); else tmp_name = g_build_filename (main_path_stats (), "scores_fluid.txt", NULL); /* Open the data file */ fh = (FILE *) g_fopen (tmp_name, "r"); if (!fh) { g_message ("no data yet, no statistic file:\n%s", tmp_name); g_free (tmp_name); return; } /* Reads the first line (header) */ if (!fgets (tmp_str, 2000, fh)) { g_message ("no data on the statistic file:\n%s", tmp_name); g_free (tmp_name); fclose (fh); return; } /* Changing to "C" locale */ tmp_locale = g_strdup (setlocale (LC_NUMERIC, NULL)); if (tmp_locale != NULL) setlocale (LC_NUMERIC, "C"); /* Reads the first DATA_POINTS points */ for (i = 0; i < DATA_POINTS; i++) plot.data.y[i] = -1000; i = 0; while (1) { gint itens; if (field < 4) itens = fscanf (fh, "%f%f%f%s%s", &accur[i], &velo[i], &fluid[i], date[i], hour[i]); else itens = fscanf (fh, "%f%s%s%i", &score[i], date[i], hour[i], &nchars[i]); if (itens != 5 && field < 4) break; else if (itens != 4 && field == 4) break; if (fgets (lesson[i], 299, fh) == NULL) break; if (strlen (lesson[i]) == 299) { if (field < 4) g_warning ("file name of custom lesson too long in line %i of %s: " "the limit is 299 characters.", i, tmp_name); else g_warning ("language name too long in line %i of %s: " "the limit is 299 characters.", i, tmp_name); } //plot_clip_data (i); if (tutor_get_type () == TT_BASIC && g_ascii_strtoll (lesson[i], NULL, 10) != lesson_n) continue; switch (field) { case 1: plot.data.y[i] = accur[i]; break; case 2: plot.data.y[i] = velo[i]; break; case 3: plot.data.y[i] = fluid[i]; break; case 4: plot.data.y[i] = score[i]; break; } if (++i == DATA_POINTS) break; } /* Reads until the end, keeping the last DATA_POINTS points */ while (1) { gint itens; if (field < 4) itens = fscanf (fh, "%f%f%f%s%s", &accur[i], &velo[i], &fluid[i], date[i], hour[i]); else itens = fscanf (fh, "%f%s%s%i", &score[i], date[i], hour[i], &nchars[i]); if (itens != 5 && field < 4) break; else if (itens != 4 && field == 4) break; if (fgets (lesson[i], 299, fh) == NULL) break; if (strlen (lesson[i]) == 299) { if (field < 4) g_warning ("file name of custom lesson too long in line %i of %s: " "the limit is 299 characters.", i, tmp_name); else g_warning ("language name too long in line %i of %s: " "the limit is 299 characters.", i, tmp_name); } //plot_clip_data (i); if (tutor_get_type () == TT_BASIC && g_ascii_strtoll (lesson[i], NULL, 10) != lesson_n) continue; for (i = 0; i < DATA_POINTS - 1; i++) { plot.data.y[i] = plot.data.y[i + 1]; strcpy (date[i], date[i + 1]); strcpy (hour[i], hour[i + 1]); } strcpy (date[i], date[i + 1]); strcpy (hour[i], hour[i + 1]); switch (field) { case 1: plot.data.y[i] = accur[i + 1]; break; case 2: plot.data.y[i] = velo[i + 1]; break; case 3: plot.data.y[i] = fluid[i + 1]; break; case 4: plot.data.y[i] = score[i + 1]; break; } i = DATA_POINTS; } fclose (fh); g_free (tmp_name); /* Coming back to the right locale */ if (tmp_locale != NULL) setlocale (LC_NUMERIC, tmp_locale); g_free (tmp_locale); if (i == 0) { g_message ("no valid data to plot."); gdk_color_parse ("#ccccce", &color); gtk_widget_modify_bg (plot.databox, GTK_STATE_NORMAL, &color); return; } else { gdk_color_parse ("#ffffff", &color); gtk_widget_modify_bg (plot.databox, GTK_STATE_NORMAL, &color); } n_points = i; /* Format the chart */ plot.mark.x[0] = -7; plot.mark.y[0] = -7; plot.goal.x[0] = plot.lim.x[0] = 0; plot.goal.x[1] = plot.lim.x[1] = DATA_POINTS + 2; plot.lim.y[0] = 0; plot.lim.y[1] = 100; gdk_color_parse ("#000000", &color_black); switch (field) { case 1: plot.lim.y[0] = 60; plot.goal.y[0] = tutor_goal_accuracy (); gdk_color_parse (PLOT_GREEN, &color); gdk_color_parse (PLOT_GREEN_2, &color2); gdk_color_parse (PLOT_GREEN_3, &color3); break; case 2: plot.goal.y[0] = tutor_goal_speed (); gdk_color_parse (PLOT_RED, &color); gdk_color_parse (PLOT_RED_2, &color2); gdk_color_parse (PLOT_RED_3, &color3); break; case 3: plot.goal.y[0] = tutor_goal_fluidity (); gdk_color_parse (PLOT_BLUE, &color); gdk_color_parse (PLOT_BLUE_2, &color2); gdk_color_parse (PLOT_BLUE_3, &color3); break; case 4: plot.lim.y[1] = 10; plot.goal.y[0] = -1; gdk_color_parse (PLOT_ORANGE, &color); gdk_color_parse (PLOT_ORANGE_2, &color2); gdk_color_parse (PLOT_ORANGE_3, &color3); } plot.goal.y[1] = plot.goal.y[0]; /* Point limits */ plot.limits = gtk_databox_points_new (2, plot.lim.x, plot.lim.y, &color_black, 1); gtk_databox_graph_add (box, plot.limits); gtk_databox_auto_rescale (box, 0.0); //gtk_databox_set_total_limits (box, plot.lim.x[0], plot.lim.x[1], plot.lim.y[0], plot.lim.y[1]); //g_message ("(%f, %f) / (%f, %f)", plot.lim.x[0], plot.lim.x[1], plot.lim.y[0], plot.lim.y[1]); /* Point kernel */ plot.point_kernel = gtk_databox_points_new (i, plot.data.x, plot.data.y, &color, 3); gtk_databox_graph_add (box, plot.point_kernel); /* Point frame */ plot.point_frame = gtk_databox_points_new (i, plot.data.x, plot.data.y, &color_black, 5); gtk_databox_graph_add (box, plot.point_frame); /* Data marker */ plot.point_marker = gtk_databox_points_new (1, plot.mark.x, plot.mark.y, &color_black, 7); gtk_databox_graph_add (box, plot.point_marker); /* Kernel line */ plot.line_kernel = gtk_databox_lines_new (i, plot.data.x, plot.data.y, &color, 1); gtk_databox_graph_add (box, plot.line_kernel); /* Frame line */ plot.line_frame = gtk_databox_lines_new (i, plot.data.x, plot.data.y, &color2, 3); gtk_databox_graph_add (box, plot.line_frame); /* Outter line */ plot.line_outter = gtk_databox_lines_new (i, plot.data.x, plot.data.y, &color3, 5); gtk_databox_graph_add (box, plot.line_outter); /* Goal limit */ gdk_color_parse ("#999999", &color3); plot.line_goal = gtk_databox_lines_new (2, plot.goal.x, plot.goal.y, &color3, 1); gtk_databox_graph_add (box, plot.line_goal); /* Grid */ gdk_color_parse ("#dddddd", &color); if (field == 1) plot.grid = gtk_databox_grid_new (3, 3, &color, 1); else plot.grid = gtk_databox_grid_new (9, 3, &color, 1); gtk_databox_graph_add (GTK_DATABOX (plot.databox), plot.grid); /* Redraw the plot */ gtk_widget_show_all (plot.table); } void plot_pointer_update (gdouble x) { static glong n_prev = 0; glong n = 0; gchar *xstr; gchar *ystr; GtkDatabox *box; gint width = 600; box = GTK_DATABOX (plot.databox); gdk_drawable_get_size (GDK_DRAWABLE (gtk_databox_get_backing_pixmap (box)), &width, NULL); n = rintf (x / width * (DATA_POINTS + 2)) - 1; if (n == n_prev) return; n_prev = n; if (n < 0 || n >= n_points || n >= DATA_POINTS) { xstr = g_strdup (""); ystr = g_strdup (""); plot.mark.x[0] = -7; plot.mark.y[0] = -7; } else { if (plot_type < 6) { xstr = g_strdup_printf ("%s - %s", date[n], hour[n]); ystr = g_strdup_printf ("%.2f", plot.data.y[n]); } else { xstr = accur_char_utf8 (n); ystr = g_strdup_printf ("%.0f/%.lu", plot.data.y[n], accur_error_total ()); } plot.mark.x[0] = plot.data.x[n]; plot.mark.y[0] = plot.data.y[n]; } gtk_entry_set_text (GTK_ENTRY (get_wg ("entry_stat_x")), xstr); gtk_entry_set_text (GTK_ENTRY (get_wg ("entry_stat_y")), ystr); gtk_widget_queue_draw (plot.databox); g_free (xstr); g_free (ystr); }