[gnome-disk-utility/ata-smart-ui-rework] More work in progress for new ATA smart graphs



commit 06d3d26bc57565e30b0fde28514149e0f3d1ba92
Author: David Zeuthen <davidz redhat com>
Date:   Fri Jul 10 17:52:25 2009 -0400

    More work in progress for new ATA smart graphs

 src/gdu-gtk/gdu-ata-smart-dialog.c |  181 ++++--
 src/gdu-gtk/gdu-curve.c            |   98 ++-
 src/gdu-gtk/gdu-curve.h            |   23 +-
 src/gdu-gtk/gdu-graph.c            | 1205 +++++++++++++++++++++++++-----------
 src/gdu-gtk/gdu-graph.h            |   16 +-
 src/gdu-gtk/gdu-gtk-enums.h        |   18 +-
 src/gdu-gtk/gdu-gtk-types.h        |    2 +-
 7 files changed, 1048 insertions(+), 495 deletions(-)
---
diff --git a/src/gdu-gtk/gdu-ata-smart-dialog.c b/src/gdu-gtk/gdu-ata-smart-dialog.c
index 50339cb..853618c 100644
--- a/src/gdu-gtk/gdu-ata-smart-dialog.c
+++ b/src/gdu-gtk/gdu-ata-smart-dialog.c
@@ -165,18 +165,18 @@ selection_changed (GtkTreeSelection *tree_selection,
 
         g_debug ("selected %s", attr_name);
 
-        GArray *cur_points;
-        GArray *worst_points;
-        GArray *threshold_points;
-        GArray *raw_points;
-        GArray *band_points;
+        GArray *cur_samples;
+        GArray *worst_samples;
+        GArray *threshold_samples;
+        GArray *raw_samples;
+        GArray *band_samples;
         GList *l;
         guint64 now;
-        cur_points = g_array_new (FALSE, FALSE, sizeof (GduPoint));
-        worst_points = g_array_new (FALSE, FALSE, sizeof (GduPoint));
-        threshold_points = g_array_new (FALSE, FALSE, sizeof (GduPoint));
-        raw_points = g_array_new (FALSE, FALSE, sizeof (GduPoint));
-        band_points = g_array_new (FALSE, FALSE, sizeof (GduPoint));
+        cur_samples = g_array_new (FALSE, FALSE, sizeof (GduSample));
+        worst_samples = g_array_new (FALSE, FALSE, sizeof (GduSample));
+        threshold_samples = g_array_new (FALSE, FALSE, sizeof (GduSample));
+        raw_samples = g_array_new (FALSE, FALSE, sizeof (GduSample));
+        band_samples = g_array_new (FALSE, FALSE, sizeof (GduSample));
 
         guint64 raw_min;
         guint64 raw_max;
@@ -236,7 +236,7 @@ selection_changed (GtkTreeSelection *tree_selection,
                 break;
         }
 
-
+#if 0
         gchar **y_axis_left;
         y_axis_left = g_new0 (gchar *, 6);
         for (n = 0; n < 5; n++) {
@@ -251,6 +251,7 @@ selection_changed (GtkTreeSelection *tree_selection,
         y_axis_left[n] = NULL;
         gdu_graph_set_y_markers_left (GDU_GRAPH (dialog->priv->graph), (const gchar* const *) y_axis_left);
         g_strfreev (y_axis_left);
+#endif
 
         guint64 tolerance;
         guint64 timespan;
@@ -262,38 +263,38 @@ selection_changed (GtkTreeSelection *tree_selection,
         now = (guint64) time (NULL);
         last_age = timespan;
 
-        /* oldest points first */
+        /* oldest samples first */
         for (l = dialog->priv->historical_data; l != NULL; l = l->next) {
                 GduAtaSmartHistoricalData *data = GDU_ATA_SMART_HISTORICAL_DATA (l->data);
                 GduAtaSmartAttribute *attr;
                 guint64 time_collected;
-                GduPoint point;
+                GduSample sample;
                 guint64 age;
-                gboolean use_point;
+                gboolean use_sample;
 
-                memset (&point, '\0', sizeof (GduPoint));
+                memset (&sample, '\0', sizeof (GduSample));
 
                 time_collected = gdu_ata_smart_historical_data_get_time_collected (data);
                 age = now - time_collected;
 
-                /* skip old points, except if the following point is not too old */
-                use_point = FALSE;
+                /* skip old samples, except if the following sample is not too old */
+                use_sample = FALSE;
                 if (age < timespan) {
-                        use_point = TRUE;
+                        use_sample = TRUE;
                 } else {
                         if (l->next != NULL) {
                                 GduAtaSmartHistoricalData *next_data = GDU_ATA_SMART_HISTORICAL_DATA (l->next->data);
                                 guint64 next_age;
                                 next_age = now - gdu_ata_smart_historical_data_get_time_collected (next_data);
                                 if (next_age < timespan) {
-                                        use_point = TRUE;
+                                        use_sample = TRUE;
                                 }
                         }
                 }
 
-                if (use_point) {
+                if (use_sample) {
 
-                        point.x = 1.0 - ((gdouble) age) / ((gdouble) timespan);
+                        sample.time_usec = time_collected * 1000L * 1000L;
 
                         attr = gdu_ata_smart_historical_data_get_attribute (data, attr_name);
                         if (attr != NULL) {
@@ -307,41 +308,35 @@ selection_changed (GtkTreeSelection *tree_selection,
                                 threshold = gdu_ata_smart_attribute_get_threshold (attr);
                                 raw = gdu_ata_smart_attribute_get_pretty_value (attr);
 
-                                point.y = current / 255.0f;
-                                g_array_append_val (cur_points, point);
+                                sample.value = current / 255.0f;
+                                g_array_append_val (cur_samples, sample);
 
-                                point.y = worst / 255.0f;
-                                g_array_append_val (worst_points, point);
+                                sample.value = worst / 255.0f;
+                                g_array_append_val (worst_samples, sample);
 
-                                point.y = threshold / 255.0f;
-                                g_array_append_val (threshold_points, point);
+                                sample.value = threshold / 255.0f;
+                                g_array_append_val (threshold_samples, sample);
 
-                                point.y = ((gdouble) (raw - raw_min)) / ((gdouble) (raw_max - raw_min));
-                                g_array_append_val (raw_points, point);
+                                sample.value = ((gdouble) (raw - raw_min)) / ((gdouble) (raw_max - raw_min));
+                                g_array_append_val (raw_samples, sample);
 
                                 g_object_unref (attr);
                         }
 
                         /* draw a band if there's a discontinuity; e.g. no samples for an hour or more */
                         if (last_age - age >= tolerance) {
-                                guint64 band_start;
-                                guint64 band_end;
-
-                                band_start = last_age - tolerance;
-                                band_end = age + tolerance;
-
-                                point.x = 1.0 - band_start / ((gdouble) timespan);
-                                point.y = 1.0;
-                                g_array_append_val (band_points, point);
+                                sample.time_usec = (now - last_age + tolerance) * 1000 * 1000;
+                                sample.value = 1.0;
+                                g_array_append_val (band_samples, sample);
 
-                                point.x = 1.0 - band_end / ((gdouble) timespan);
-                                point.y = 1.0;
-                                g_array_append_val (band_points, point);
+                                sample.time_usec = (now - age - tolerance) * 1000 * 1000;
+                                sample.value = 1.0;
+                                g_array_append_val (band_samples, sample);
 
                                 /* close the segment */
-                                point.x = G_MAXDOUBLE;
-                                point.y = G_MAXDOUBLE;
-                                g_array_append_val (band_points, point);
+                                sample.time_usec = G_MAXINT64;
+                                sample.value = G_MAXDOUBLE;
+                                g_array_append_val (band_samples, sample);
                         }
                 }
 
@@ -353,12 +348,13 @@ selection_changed (GtkTreeSelection *tree_selection,
                 (((argb_hex) >> 16)&0xff) / 255.0,      \
                 (((argb_hex) >>  8)&0xff) / 255.0,      \
                 (((argb_hex) >>  0)&0xff) / 255.0,      \
-                alpha                                   \
+                alpha / 255.0                           \
         }
 
         /* see http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines for colors
          */
         GduColor raw_color       = GDU_COLOR_FROM_HEX (0xfcaf3e, 255.0); /* orange */
+        GduColor raw_fill_color  = GDU_COLOR_FROM_HEX (0xfcaf3e, 128.0); /* orange */
         GduColor cur_color       = GDU_COLOR_FROM_HEX (0x729fcf, 255.0); /* sky blue */
         GduColor worst_color     = GDU_COLOR_FROM_HEX (0xad7fa8, 255.0); /* plum */
         GduColor threshold_color = GDU_COLOR_FROM_HEX (0xef2929, 255.0); /* scarlet red */
@@ -369,11 +365,11 @@ selection_changed (GtkTreeSelection *tree_selection,
         /* add graphs in order */
         gint z_order = 0;
 
-        /* first the bands representing no data */
+        /* bands representing no data */
         c = gdu_curve_new ();
         gdu_curve_set_legend (c, _("No data"));
         gdu_curve_set_z_order (c, z_order++);
-        gdu_curve_set_points (c, band_points);
+        gdu_curve_set_samples (c, band_samples);
         gdu_curve_set_color (c, &band_color);
         gdu_curve_set_fill_color (c, &band_fill_color);
         gdu_curve_set_flags (c, GDU_CURVE_FLAGS_FILLED | GDU_CURVE_FLAGS_FADE_EDGES);
@@ -382,11 +378,11 @@ selection_changed (GtkTreeSelection *tree_selection,
                              c);
         g_object_unref (c);
 
-        /* then worst */
+        /* worst */
         c = gdu_curve_new ();
         gdu_curve_set_legend (c, _("Worst"));
         gdu_curve_set_z_order (c, z_order++);
-        gdu_curve_set_points (c, worst_points);
+        gdu_curve_set_samples (c, worst_samples);
         gdu_curve_set_color (c, &worst_color);
         gdu_curve_set_width (c, 1.0);
         gdu_graph_add_curve (GDU_GRAPH (dialog->priv->graph),
@@ -394,11 +390,11 @@ selection_changed (GtkTreeSelection *tree_selection,
                              c);
         g_object_unref (c);
 
-        /* then threshold */
+        /* threshold */
         c = gdu_curve_new ();
         gdu_curve_set_legend (c, _("Treshold"));
         gdu_curve_set_z_order (c, z_order++);
-        gdu_curve_set_points (c, threshold_points);
+        gdu_curve_set_samples (c, threshold_samples);
         gdu_curve_set_color (c, &threshold_color);
         gdu_curve_set_width (c, 1.0);
         gdu_graph_add_curve (GDU_GRAPH (dialog->priv->graph),
@@ -406,11 +402,11 @@ selection_changed (GtkTreeSelection *tree_selection,
                              c);
         g_object_unref (c);
 
-        /* then current */
+        /* current */
         c = gdu_curve_new ();
         gdu_curve_set_legend (c, _("Current"));
         gdu_curve_set_z_order (c, z_order++);
-        gdu_curve_set_points (c, cur_points);
+        gdu_curve_set_samples (c, cur_samples);
         gdu_curve_set_color (c, &cur_color);
         gdu_curve_set_width (c, 2.0);
         gdu_graph_add_curve (GDU_GRAPH (dialog->priv->graph),
@@ -419,29 +415,67 @@ selection_changed (GtkTreeSelection *tree_selection,
         g_object_unref (c);
 
 
-        /* then raw */
+        /* raw */
         c = gdu_curve_new ();
         gdu_curve_set_legend (c, _("Raw")); /* TODO: units? */
         gdu_curve_set_z_order (c, z_order++);
-        gdu_curve_set_points (c, raw_points);
+        gdu_curve_set_samples (c, raw_samples);
         gdu_curve_set_color (c, &raw_color);
+        gdu_curve_set_fill_color (c, &raw_fill_color);
         gdu_curve_set_width (c, 2.0);
+        gdu_curve_set_flags (c,
+                             GDU_CURVE_FLAGS_AXIS_MARKERS_LEFT |
+                             GDU_CURVE_FLAGS_FILLED |
+                             GDU_CURVE_FLAGS_NORMALIZE);
         gdu_graph_add_curve (GDU_GRAPH (dialog->priv->graph),
                              "raw",
                              c);
         g_object_unref (c);
 
-        g_array_unref (cur_points);
-        g_array_unref (worst_points);
-        g_array_unref (threshold_points);
-        g_array_unref (raw_points);
-        g_array_unref (band_points);
+        g_array_unref (cur_samples);
+        g_array_unref (worst_samples);
+        g_array_unref (threshold_samples);
+        g_array_unref (raw_samples);
+        g_array_unref (band_samples);
 
  out:
         g_free (attr_name);
 }
 
 static void
+on_period_scale_changed (GtkRange *range,
+                         gpointer  user_data)
+{
+        gdouble value;
+        GduAtaSmartDialog *dialog = GDU_ATA_SMART_DIALOG (user_data);
+
+        value = gtk_range_get_value (range);
+
+        gdu_graph_set_window_size_usec (GDU_GRAPH (dialog->priv->graph),
+                                        value * 60 * 60 * G_USEC_PER_SEC);
+}
+
+static void
+on_end_scale_changed (GtkRange *range,
+                      gpointer  user_data)
+{
+        gdouble value;
+        GduAtaSmartDialog *dialog = GDU_ATA_SMART_DIALOG (user_data);
+        gint64 now_usec;
+
+        now_usec = time (NULL) * G_USEC_PER_SEC;
+
+        value = gtk_range_get_value (range);
+        if (value < 1.0) {
+                gdu_graph_set_window_end_usec (GDU_GRAPH (dialog->priv->graph),
+                                               G_MAXINT64);
+        } else {
+                gdu_graph_set_window_end_usec (GDU_GRAPH (dialog->priv->graph),
+                                               now_usec - value * 60 * 60 * G_USEC_PER_SEC);
+        }
+}
+
+static void
 gdu_ata_smart_dialog_constructed (GObject *object)
 {
         GduAtaSmartDialog *dialog = GDU_ATA_SMART_DIALOG (object);
@@ -498,9 +532,10 @@ gdu_ata_smart_dialog_constructed (GObject *object)
 
         graph = gdu_graph_new ();
         dialog->priv->graph = graph;
-        gtk_widget_set_size_request (graph, 480, 250);
+        gtk_widget_set_size_request (graph, 480, 350);
         gtk_box_pack_start (GTK_BOX (hbox), graph, TRUE, TRUE, 0);
 
+#if 0
         const gchar *time_axis[7];
         time_axis[0] = C_("ATA SMART graph label", "five days ago");
         time_axis[1] = C_("ATA SMART graph label", "four days ago");
@@ -519,6 +554,7 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         y_axis_right[4] = C_("ATA SMART graph label", "255");
         y_axis_right[5] = NULL;
         gdu_graph_set_y_markers_right (GDU_GRAPH (graph), y_axis_right);
+#endif
 
         row = 0;
 
@@ -778,6 +814,29 @@ gdu_ata_smart_dialog_constructed (GObject *object)
                                                                            G_CALLBACK (device_changed),
                                                                            dialog);
 
+        GtkWidget *scale;
+        scale = gtk_hscale_new_with_range (2.0,
+                                           365 * 24.0,
+                                           1.0);
+        gtk_scale_set_digits (GTK_SCALE (scale), 0);
+        gtk_scale_set_draw_value (GTK_SCALE (scale), TRUE);
+	gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+        g_signal_connect (scale,
+                          "value-changed",
+                          G_CALLBACK (on_period_scale_changed),
+                          dialog);
+
+        scale = gtk_hscale_new_with_range (0.0,
+                                           365 * 24.0,
+                                           1.0);
+        gtk_scale_set_digits (GTK_SCALE (scale), 0);
+        gtk_scale_set_draw_value (GTK_SCALE (scale), TRUE);
+	gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+        g_signal_connect (scale,
+                          "value-changed",
+                          G_CALLBACK (on_end_scale_changed),
+                          dialog);
+
         if (G_OBJECT_CLASS (gdu_ata_smart_dialog_parent_class)->constructed != NULL)
                 G_OBJECT_CLASS (gdu_ata_smart_dialog_parent_class)->constructed (object);
 }
diff --git a/src/gdu-gtk/gdu-curve.c b/src/gdu-gtk/gdu-curve.c
index cc7a5a0..545c789 100644
--- a/src/gdu-gtk/gdu-curve.c
+++ b/src/gdu-gtk/gdu-curve.c
@@ -31,7 +31,8 @@
 struct GduCurvePrivate
 {
         GduCurveFlags flags;
-        GArray *points;
+        GduCurveUnit unit;
+        GArray *samples;
         gint z_order;
         GduColor *color;
         GduColor *fill_color;
@@ -45,7 +46,8 @@ enum
 {
         PROP_0,
         PROP_FLAGS,
-        PROP_POINTS,
+        PROP_UNIT,
+        PROP_SAMPLES,
         PROP_Z_ORDER,
         PROP_COLOR,
         PROP_FILL_COLOR,
@@ -66,8 +68,12 @@ gdu_curve_set_property (GObject      *object,
                 gdu_curve_set_flags (curve, g_value_get_flags (value));
                 break;
 
-        case PROP_POINTS:
-                gdu_curve_set_points (curve, g_value_get_boxed (value));
+        case PROP_UNIT:
+                gdu_curve_set_unit (curve, g_value_get_enum (value));
+                break;
+
+        case PROP_SAMPLES:
+                gdu_curve_set_samples (curve, g_value_get_boxed (value));
                 break;
 
         case PROP_Z_ORDER:
@@ -109,8 +115,12 @@ gdu_curve_get_property (GObject     *object,
                 g_value_set_flags (value, gdu_curve_get_flags (curve));
                 break;
 
-        case PROP_POINTS:
-                g_value_set_boxed (value, gdu_curve_get_points (curve));
+        case PROP_UNIT:
+                g_value_set_enum (value, gdu_curve_get_unit (curve));
+                break;
+
+        case PROP_SAMPLES:
+                g_value_set_boxed (value, gdu_curve_get_samples (curve));
                 break;
 
         case PROP_Z_ORDER:
@@ -148,8 +158,8 @@ gdu_curve_finalize (GObject *object)
                 gdu_color_free (curve->priv->color);
         if (curve->priv->fill_color != NULL)
                 gdu_color_free (curve->priv->fill_color);
-        if (curve->priv->points != NULL)
-                g_array_unref (curve->priv->points);
+        if (curve->priv->samples != NULL)
+                g_array_unref (curve->priv->samples);
 
         g_free (curve->priv->legend);
 
@@ -196,10 +206,21 @@ gdu_curve_class_init (GduCurveClass *klass)
                                                              G_PARAM_CONSTRUCT));
 
         g_object_class_install_property (gobject_class,
-                                         PROP_POINTS,
-                                         g_param_spec_boxed ("points",
-                                                             _("Points"),
-                                                             _("The points of the curve"),
+                                         PROP_UNIT,
+                                         g_param_spec_enum ("unit",
+                                                            _("Unit"),
+                                                            _("The unit used for the curve"),
+                                                            GDU_TYPE_CURVE_UNIT,
+                                                            GDU_CURVE_UNIT_NUMBER,
+                                                            G_PARAM_READABLE |
+                                                            G_PARAM_WRITABLE |
+                                                            G_PARAM_CONSTRUCT));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_SAMPLES,
+                                         g_param_spec_boxed ("samples",
+                                                             _("Samples"),
+                                                             _("The samples of the curve"),
                                                              G_TYPE_ARRAY,
                                                              G_PARAM_READABLE |
                                                              G_PARAM_WRITABLE));
@@ -279,11 +300,18 @@ gdu_curve_get_flags (GduCurve *curve)
         return curve->priv->flags;
 }
 
+GduCurveUnit
+gdu_curve_get_unit (GduCurve *curve)
+{
+        g_return_val_if_fail (GDU_IS_CURVE (curve), G_MAXINT);
+        return curve->priv->unit;
+}
+
 GArray *
-gdu_curve_get_points (GduCurve *curve)
+gdu_curve_get_samples (GduCurve *curve)
 {
         g_return_val_if_fail (GDU_IS_CURVE (curve), NULL);
-        return curve->priv->points;
+        return curve->priv->samples;
 }
 
 gint
@@ -324,22 +352,30 @@ gdu_curve_get_legend (GduCurve *curve)
 /* ---------------------------------------------------------------------------------------------------- */
 
 void
-gdu_curve_set_flags (GduCurve     *curve,
-                    GduCurveFlags  flags)
+gdu_curve_set_flags (GduCurve      *curve,
+                     GduCurveFlags  flags)
 {
         g_return_if_fail (GDU_IS_CURVE (curve));
         curve->priv->flags = flags;
 }
 
 void
-gdu_curve_set_points (GduCurve *curve,
-                      GArray   *points)
+gdu_curve_set_unit (GduCurve     *curve,
+                    GduCurveUnit  unit)
+{
+        g_return_if_fail (GDU_IS_CURVE (curve));
+        curve->priv->unit = unit;
+}
+
+void
+gdu_curve_set_samples (GduCurve *curve,
+                       GArray   *samples)
 {
         g_return_if_fail (GDU_IS_CURVE (curve));
 
-        if (curve->priv->points != NULL)
-                g_array_unref (curve->priv->points);
-        curve->priv->points = points != NULL ? g_array_ref (points) : NULL;
+        if (curve->priv->samples != NULL)
+                g_array_unref (curve->priv->samples);
+        curve->priv->samples = samples != NULL ? g_array_ref (samples) : NULL;
 }
 
 void
@@ -430,31 +466,31 @@ gdu_color_get_type (void)
   return g_define_type_id__volatile;
 }
 
-GduPoint *
-gdu_point_dup (GduPoint *point)
+GduSample *
+gdu_sample_dup (GduSample *sample)
 {
-        GduPoint *p;
-        p = g_memdup (point, sizeof (GduPoint));
+        GduSample *p;
+        p = g_memdup (sample, sizeof (GduSample));
         return p;
 }
 
 void
-gdu_point_free (GduPoint *point)
+gdu_sample_free (GduSample *sample)
 {
-        g_free (point);
+        g_free (sample);
 }
 
 GType
-gdu_point_get_type (void)
+gdu_sample_get_type (void)
 {
   static volatile gsize g_define_type_id__volatile = 0;
 
   if (g_once_init_enter (&g_define_type_id__volatile))
     {
       GType g_define_type_id =
-        g_boxed_type_register_static ("GduPoint",
-                                      (GBoxedCopyFunc) gdu_point_dup,
-                                      (GBoxedFreeFunc) gdu_point_free);
+        g_boxed_type_register_static ("GduSample",
+                                      (GBoxedCopyFunc) gdu_sample_dup,
+                                      (GBoxedFreeFunc) gdu_sample_free);
 
       g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
     }
diff --git a/src/gdu-gtk/gdu-curve.h b/src/gdu-gtk/gdu-curve.h
index 294695d..fc52017 100644
--- a/src/gdu-gtk/gdu-curve.h
+++ b/src/gdu-gtk/gdu-curve.h
@@ -55,7 +55,8 @@ GType          gdu_curve_get_type            (void) G_GNUC_CONST;
 GduCurve      *gdu_curve_new                 (void);
 
 GduCurveFlags  gdu_curve_get_flags           (GduCurve      *curve);
-GArray        *gdu_curve_get_points          (GduCurve      *curve);
+GduCurveUnit   gdu_curve_get_unit            (GduCurve      *curve);
+GArray        *gdu_curve_get_samples         (GduCurve      *curve);
 gint           gdu_curve_get_z_order         (GduCurve      *curve);
 GduColor      *gdu_curve_get_color           (GduCurve      *curve);
 GduColor      *gdu_curve_get_fill_color      (GduCurve      *curve);
@@ -64,8 +65,10 @@ const gchar   *gdu_curve_get_legend          (GduCurve      *curve);
 
 void           gdu_curve_set_flags           (GduCurve      *curve,
                                               GduCurveFlags  flags);
-void           gdu_curve_set_points          (GduCurve      *curve,
-                                              GArray        *points);
+void           gdu_curve_set_unit            (GduCurve      *curve,
+                                              GduCurveUnit   unit);
+void           gdu_curve_set_samples         (GduCurve      *curve,
+                                              GArray        *samples);
 void           gdu_curve_set_z_order         (GduCurve      *curve,
                                               gint           z_order);
 void           gdu_curve_set_color           (GduCurve      *curve,
@@ -79,16 +82,16 @@ void           gdu_curve_set_legend          (GduCurve      *curve,
 
 /* ----------------------------------------------------------------------------------------------------  */
 
-struct GduPoint
+struct GduSample
 {
-        gdouble x;
-        gdouble y;
+        gint64 time_usec;
+        gdouble value;
 };
 
-#define GDU_TYPE_POINT (gdu_point_get_type ())
-GType     gdu_point_get_type (void) G_GNUC_CONST;
-GduPoint *gdu_point_dup      (GduPoint *point);
-void      gdu_point_free     (GduPoint *point);
+#define GDU_TYPE_SAMPLE (gdu_sample_get_type ())
+GType      gdu_sample_get_type (void) G_GNUC_CONST;
+GduSample *gdu_sample_dup      (GduSample *sample);
+void       gdu_sample_free     (GduSample *sample);
 
 struct GduColor
 {
diff --git a/src/gdu-gtk/gdu-graph.c b/src/gdu-gtk/gdu-graph.c
index d04f969..3a16097 100644
--- a/src/gdu-gtk/gdu-graph.c
+++ b/src/gdu-gtk/gdu-graph.c
@@ -31,13 +31,10 @@
 
 struct GduGraphPrivate
 {
-        guint foo;
-
-        gchar **x_markers;
-        gchar **y_markers_left;
-        gchar **y_markers_right;
-
         GHashTable *curves;
+
+        gint64 window_end_usec;
+        gint64 window_size_usec;
 };
 
 G_DEFINE_TYPE (GduGraph, gdu_graph, GTK_TYPE_DRAWING_AREA)
@@ -48,9 +45,8 @@ static gboolean gdu_graph_expose_event (GtkWidget      *widget,
 enum
 {
         PROP_0,
-        PROP_X_MARKERS,
-        PROP_Y_MARKERS_LEFT,
-        PROP_Y_MARKERS_RIGHT,
+        PROP_WINDOW_END_USEC,
+        PROP_WINDOW_SIZE_USEC,
 };
 
 static void
@@ -62,16 +58,12 @@ gdu_graph_set_property (GObject      *object,
         GduGraph *graph = GDU_GRAPH (object);
 
         switch (prop_id) {
-        case PROP_X_MARKERS:
-                gdu_graph_set_x_markers (graph, g_value_get_boxed (value));
+        case PROP_WINDOW_END_USEC:
+                gdu_graph_set_window_end_usec (graph, g_value_get_int64 (value));
                 break;
 
-        case PROP_Y_MARKERS_LEFT:
-                gdu_graph_set_y_markers_left (graph, g_value_get_boxed (value));
-                break;
-
-        case PROP_Y_MARKERS_RIGHT:
-                gdu_graph_set_y_markers_right (graph, g_value_get_boxed (value));
+        case PROP_WINDOW_SIZE_USEC:
+                gdu_graph_set_window_size_usec (graph, g_value_get_int64 (value));
                 break;
 
         default:
@@ -89,16 +81,12 @@ gdu_graph_get_property (GObject     *object,
         GduGraph *graph = GDU_GRAPH (object);
 
         switch (prop_id) {
-        case PROP_X_MARKERS:
-                g_value_set_boxed (value, graph->priv->x_markers);
+        case PROP_WINDOW_END_USEC:
+                g_value_set_int64 (value, graph->priv->window_end_usec);
                 break;
 
-        case PROP_Y_MARKERS_LEFT:
-                g_value_set_boxed (value, graph->priv->y_markers_left);
-                break;
-
-        case PROP_Y_MARKERS_RIGHT:
-                g_value_set_boxed (value, graph->priv->y_markers_right);
+        case PROP_WINDOW_SIZE_USEC:
+                g_value_set_int64 (value, graph->priv->window_size_usec);
                 break;
 
         default:
@@ -112,10 +100,6 @@ gdu_graph_finalize (GObject *object)
 {
         GduGraph *graph = GDU_GRAPH (object);
 
-        g_strfreev (graph->priv->x_markers);
-        g_strfreev (graph->priv->y_markers_left);
-        g_strfreev (graph->priv->y_markers_right);
-
         g_hash_table_unref (graph->priv->curves);
 
         if (G_OBJECT_CLASS (gdu_graph_parent_class)->finalize != NULL)
@@ -132,36 +116,31 @@ gdu_graph_class_init (GduGraphClass *klass)
         gobject_class->set_property = gdu_graph_set_property;
         gobject_class->get_property = gdu_graph_get_property;
 
-        widget_class->expose_event  = gdu_graph_expose_event;
+        widget_class->expose_event       = gdu_graph_expose_event;
 
         g_type_class_add_private (klass, sizeof (GduGraphPrivate));
 
         g_object_class_install_property (gobject_class,
-                                         PROP_X_MARKERS,
-                                         g_param_spec_boxed ("x-markers",
-                                                             _("X Markers"),
-                                                             _("Markers to print on the X axis"),
-                                                             G_TYPE_STRV,
+                                         PROP_WINDOW_END_USEC,
+                                         g_param_spec_int64 ("window-end-usec",
+                                                             _("Window End Microseconds"),
+                                                             _("The end of the graph window, in micro-seconds since epoch"),
+                                                             G_MININT64,
+                                                             G_MAXINT64,
+                                                             G_MAXINT64,
                                                              G_PARAM_READABLE |
                                                              G_PARAM_WRITABLE |
                                                              G_PARAM_CONSTRUCT));
 
-        g_object_class_install_property (gobject_class,
-                                         PROP_Y_MARKERS_LEFT,
-                                         g_param_spec_boxed ("y-markers-left",
-                                                             _("Left Y Markers"),
-                                                             _("Markers to print on the left Y axis"),
-                                                             G_TYPE_STRV,
-                                                             G_PARAM_READABLE |
-                                                             G_PARAM_WRITABLE |
-                                                             G_PARAM_CONSTRUCT));
 
         g_object_class_install_property (gobject_class,
-                                         PROP_Y_MARKERS_RIGHT,
-                                         g_param_spec_boxed ("y-markers-right",
-                                                             _("Right Y Markers"),
-                                                             _("Markers to print on the right Y axis"),
-                                                             G_TYPE_STRV,
+                                         PROP_WINDOW_SIZE_USEC,
+                                         g_param_spec_int64 ("window-size-usec",
+                                                             _("Window Size Microseconds"),
+                                                             _("Size of graph window, in microseconds"),
+                                                             G_MININT64,
+                                                             G_MAXINT64,
+                                                             12L * 60 * 60 * G_USEC_PER_SEC,
                                                              G_PARAM_READABLE |
                                                              G_PARAM_WRITABLE |
                                                              G_PARAM_CONSTRUCT));
@@ -180,56 +159,40 @@ gdu_graph_new (void)
         return GTK_WIDGET (g_object_new (GDU_TYPE_GRAPH, NULL));
 }
 
-gchar **
-gdu_graph_get_x_markers (GduGraph *graph)
+gint64
+gdu_graph_get_window_end_usec  (GduGraph *graph)
 {
-        g_return_val_if_fail (GDU_IS_GRAPH (graph), NULL);
-        return g_strdupv (graph->priv->x_markers);
+        g_return_val_if_fail (GDU_IS_GRAPH (graph), 0);
+        return graph->priv->window_end_usec;
 }
 
-gchar **
-gdu_graph_get_y_markers_left (GduGraph *graph)
+gint64
+gdu_graph_get_window_size_usec (GduGraph *graph)
 {
-        g_return_val_if_fail (GDU_IS_GRAPH (graph), NULL);
-        return g_strdupv (graph->priv->y_markers_left);
-}
-
-gchar **
-gdu_graph_get_y_markers_right (GduGraph *graph)
-{
-        g_return_val_if_fail (GDU_IS_GRAPH (graph), NULL);
-        return g_strdupv (graph->priv->y_markers_right);
+        g_return_val_if_fail (GDU_IS_GRAPH (graph), 0);
+        return graph->priv->window_size_usec;
 }
 
 void
-gdu_graph_set_x_markers (GduGraph           *graph,
-                         const gchar* const *markers)
+gdu_graph_set_window_end_usec  (GduGraph *graph,
+                                gint64    time_usec)
 {
         g_return_if_fail (GDU_IS_GRAPH (graph));
-        g_strfreev (graph->priv->x_markers);
-        graph->priv->x_markers = g_strdupv ((gchar **) markers);
-        if (GTK_WIDGET (graph)->window != NULL)
-                gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
-}
 
-void
-gdu_graph_set_y_markers_left (GduGraph           *graph,
-                              const gchar* const *markers)
-{
-        g_return_if_fail (GDU_IS_GRAPH (graph));
-        g_strfreev (graph->priv->y_markers_left);
-        graph->priv->y_markers_left = g_strdupv ((gchar **) markers);
+        graph->priv->window_end_usec = time_usec;
+
         if (GTK_WIDGET (graph)->window != NULL)
                 gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
 }
 
 void
-gdu_graph_set_y_markers_right (GduGraph           *graph,
-                               const gchar* const *markers)
+gdu_graph_set_window_size_usec (GduGraph *graph,
+                                gint64    period_usec)
 {
         g_return_if_fail (GDU_IS_GRAPH (graph));
-        g_strfreev (graph->priv->y_markers_right);
-        graph->priv->y_markers_right = g_strdupv ((gchar **) markers);
+
+        graph->priv->window_size_usec = period_usec;
+
         if (GTK_WIDGET (graph)->window != NULL)
                 gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
 }
@@ -248,6 +211,18 @@ measure_width (cairo_t     *cr,
         return te.width;
 }
 
+static gboolean
+str_fits_width (cairo_t *cr,
+                gdouble width_pixels,
+                const gchar *str)
+{
+        if (measure_width (cr, str) < width_pixels)
+                return TRUE;
+        return FALSE;
+}
+
+
+#if 0
 static gdouble
 measure_height (cairo_t     *cr,
                 const gchar *s)
@@ -261,6 +236,7 @@ measure_height (cairo_t     *cr,
         cairo_text_extents (cr, s, &te);
         return te.height;
 }
+#endif
 
 static void
 set_fade_edges_pattern (cairo_t   *cr,
@@ -297,339 +273,610 @@ set_fade_edges_pattern (cairo_t   *cr,
 }
 
 static gint
-compute_all_legends_width (cairo_t *cr,
-                           gdouble  lb_width,
-                           GList   *curve_list)
+curve_z_order_sort (GduCurve *a,
+                    GduCurve *b)
 {
-        GList *l;
-        gint width;
+        return gdu_curve_get_z_order (a) - gdu_curve_get_z_order (b);
+}
 
-        width = 0;
-        for (l = curve_list; l != NULL; l = l->next) {
-                GduCurve *c = GDU_CURVE (l->data);
-                cairo_text_extents_t te;
-                const gchar *text;
+typedef enum
+{
+        TIME_MARKER_TEXT_ALIGN_CENTER,
+        TIME_MARKER_TEXT_ALIGN_LEFT
+} TimeMarkerTextAlign;
 
-                text = gdu_curve_get_legend (c);
+typedef enum
+{
+        TEXT_MARKER_GRANULARITY_1_HOUR,
+        TEXT_MARKER_GRANULARITY_3_HOUR,
+        TEXT_MARKER_GRANULARITY_1_DAY,
+        TEXT_MARKER_GRANULARITY_1_WEEK,
+        TEXT_MARKER_GRANULARITY_1_MONTH,
+        TEXT_MARKER_GRANULARITY_1_YEAR,
+} TextMarkerGranularity;
+
+typedef enum
+{
+        TIME_MARKER_FLAGS_NONE         = 0,      /* No flags set */
+        TIME_MARKER_FLAGS_TEXT_AREA    = (1<<0), /* Make the line cut through the text area */
+        TIME_MARKER_FLAGS_EMPHASIS     = (1<<1), /* Emphasize the line */
+} TimeMarkerFlags;
 
-                if (text == NULL)
-                        continue;
+typedef struct
+{
+        gint64 time_usec;               /* position in time, in localtime, usec since Epoch */
 
-                cairo_select_font_face (cr, "sans",
-                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
-                cairo_set_font_size (cr, 8.0);
-                cairo_text_extents (cr, text, &te);
+        TimeMarkerFlags flags;          /* Flags affecting presentation */
 
-                width += lb_width + 3 + ceil (te.width) + 12;
-        }
+        gchar *text;                    /* Text to print, NULL for no text */
+        TimeMarkerTextAlign text_align; /* How to align the text */
+        gdouble text_width_pixels;      /* width of the area for text, in pixels */
+} TimeMarker;
 
-        return width;
+static void
+time_marker_free (TimeMarker *marker)
+{
+        g_free (marker->text);
+        g_free (marker);
 }
 
-static gint
-curve_z_order_sort (GduCurve *a,
-                    GduCurve *b)
+static gchar *
+time_marker_format_time_hour (cairo_t *cr,
+                              gdouble width_pixels,
+                              struct tm *t,
+                              struct tm *now)
 {
-        return gdu_curve_get_z_order (a) - gdu_curve_get_z_order (b);
+        gchar buf[512];
+
+        if (now->tm_year == t->tm_year &&
+            now->tm_yday == t->tm_yday) {
+                /* today */
+                strftime (buf, sizeof buf, _("%l %P"), t);
+        } else if (now->tm_year == t->tm_year &&
+                   now->tm_mon  == t->tm_mon &&
+                   (now->tm_mday - t->tm_mday) < 7) {
+                /* this week */
+
+                strftime (buf, sizeof buf, _("%A %l %P"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
+
+                strftime (buf, sizeof buf, _("%a %l %P"), t);
+
+        } else if (now->tm_year == t->tm_year) {
+                /* this year */
+
+                strftime (buf, sizeof buf, _("%B %e, %l %P"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
+
+                strftime (buf, sizeof buf, _("%b %e, %l %P"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
+
+                strftime (buf, sizeof buf, _("%b %e, %l %P"), t);
+        } else {
+                /* other year */
+
+                strftime (buf, sizeof buf, _("%B %e, %l %P, %Y"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
+
+                strftime (buf, sizeof buf, _("%b %e, %l %P, %Y"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
+
+                strftime (buf, sizeof buf, _("%b %e, %l %P, %y"), t);
+        }
+
+ out:
+        return g_strdup (buf);
 }
 
-static gboolean
-gdu_graph_expose_event (GtkWidget      *widget,
-                        GdkEventExpose *event)
+static gchar *
+time_marker_format_time_day (cairo_t *cr,
+                             gdouble width_pixels,
+                             struct tm *t,
+                             struct tm *now)
 {
-        GduGraph *graph = GDU_GRAPH (widget);
-        guint n_x_markers;
-        guint n_y_markers_left;
-        guint n_y_markers_right;
-        cairo_t *cr;
-        gdouble width, height;
-        guint n;
-        gdouble x, y;
-        guint twidth;
-        guint theight;
-        guint left_margin;
-        guint right_margin;
-        guint top_margin;
-        guint bottom_margin;
-        guint x_markers_height;
-        double gx, gy, gw, gh;
-        guint64 t_left;
-        guint64 t_right;
-        GTimeVal now;
-        GList *curve_list;
-        GList *l;
-        gdouble lb_width;
-        gdouble lb_height;
-        gdouble lb_padding;
-        gdouble lb_xpos;
-        gdouble lb_ypos;
+        gchar buf[512];
 
-        curve_list = g_hash_table_get_values (graph->priv->curves);
-        curve_list = g_list_sort (curve_list,
-                                  (GCompareFunc) curve_z_order_sort);
+        if (now->tm_year == t->tm_year) {
+                strftime (buf, sizeof buf, _("%A %B %e"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
 
-        n_x_markers = graph->priv->x_markers != NULL ? g_strv_length (graph->priv->x_markers) : 0;
-        n_y_markers_left = graph->priv->y_markers_left != NULL ? g_strv_length (graph->priv->y_markers_left) : 0;
-        n_y_markers_right = graph->priv->y_markers_right != NULL ? g_strv_length (graph->priv->y_markers_right) : 0;
+                strftime (buf, sizeof buf, _("%a %B %e"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
 
-        width = widget->allocation.width;
-        height = widget->allocation.height;
+                strftime (buf, sizeof buf, _("%b %e"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
 
-        cr = gdk_cairo_create (widget->window);
-        cairo_rectangle (cr,
-                         event->area.x, event->area.y,
-                         event->area.width, event->area.height);
-        cairo_clip (cr);
+                if (now->tm_mon == t->tm_mon) {
+                        strftime (buf, sizeof buf, _("%e"), t);
+                }
 
-        gx = 0;
-        gy = 0;
-        gw = width;
-        gh = height;
+        } else {
+                strftime (buf, sizeof buf, _("%A %B %e, %Y"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
 
-        top_margin = 10;
+                strftime (buf, sizeof buf, _("%a %B %e, %Y"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
 
-        /* measure text of all x markers */
-        bottom_margin = 0;
-        for (n = 0; n < n_x_markers; n++) {
-                theight = ceil (measure_height (cr, graph->priv->x_markers[n]));
-                if (theight > bottom_margin)
-                        bottom_margin = theight;
+                strftime (buf, sizeof buf, _("%b %e, %y"), t);
+                if (str_fits_width (cr, width_pixels, buf))
+                        goto out;
         }
-        bottom_margin += 12; /* padding */
-        x_markers_height = bottom_margin;
 
-        /* compute how much size we need for legends */
-        bottom_margin += 6; /* padding */
-        lb_height = 14;
-        lb_width = 23; /* golden ratio */
-        lb_padding = 6;
-        bottom_margin += lb_height + lb_padding;
+ out:
+        return g_strdup (buf);
+}
 
-        /* adjust drawing area */
-        gy += top_margin;
-        gh -= top_margin;
-        gh -= bottom_margin;
+static gchar *
+time_marker_format_time_week (cairo_t *cr,
+                              gdouble width_pixels,
+                              struct tm *t,
+                              struct tm *now)
+{
+        /* for now, just use the day format - we could do week numbers but... */
+        return time_marker_format_time_day (cr, width_pixels, t, now);
+}
 
-        gint all_legends_width;
-        all_legends_width = compute_all_legends_width (cr,
-                                                       lb_width,
-                                                       curve_list);
+static gchar *
+time_marker_format_time_month (cairo_t *cr,
+                               gdouble width_pixels,
+                               struct tm *t,
+                               struct tm *now)
+{
+        gchar buf[512];
 
-        /* draw legends */
-        lb_ypos = gy + gh + x_markers_height + 6; /* padding */
-        lb_xpos = 10 + ceil (((width - 20) - all_legends_width) / 2);
-        for (l = curve_list; l != NULL; l = l->next) {
-                GduCurve *c = GDU_CURVE (l->data);
-                GduColor *color;
-                GduColor *fill_color;
-                gdouble width;
-                GduCurveFlags flags;
-                const gchar *text;
-                gdouble x, y;
-                cairo_text_extents_t te;
+        strftime (buf, sizeof buf, _("%B %Y"), t);
+        if (str_fits_width (cr, width_pixels, buf))
+                goto out;
 
-                color = gdu_curve_get_color (c);
-                fill_color = gdu_curve_get_fill_color (c);
-                if (fill_color == NULL)
-                        fill_color = color;
-                width = gdu_curve_get_width (c);
-                flags = gdu_curve_get_flags (c);
-                text = gdu_curve_get_legend (c);
+         strftime (buf, sizeof buf, _("%b %y"), t);
+        if (str_fits_width (cr, width_pixels, buf))
+                goto out;
 
-                if (text == NULL)
-                        continue;
+        if (now->tm_year == t->tm_year) {
+                strftime (buf, sizeof buf, _("%b"), t);
+        }
 
-                x = lb_xpos + 0.5;
-                y = lb_ypos + 0.5;
+ out:
+        return g_strdup (buf);
+}
 
-                cairo_new_path (cr);
-                cairo_set_dash (cr, NULL, 0, 0.0);
-                cairo_set_line_width (cr, 1.0);
-                cairo_rectangle (cr, x, y, lb_width, lb_height);
-                cairo_close_path (cr);
-                cairo_set_source_rgba (cr, 1, 1, 1, 1);
-                cairo_fill_preserve (cr);
-                cairo_set_source_rgba (cr, 0, 0, 0, 1);
-                cairo_stroke (cr);
+static gchar *
+time_marker_format_time_year (cairo_t *cr,
+                              gdouble width_pixels,
+                              struct tm *t,
+                              struct tm *now)
+{
+        gchar buf[512];
+        strftime (buf, sizeof buf, _("%Y"), t);
+        return g_strdup (buf);
+}
 
-                cairo_new_path (cr);
+/* returns a list of time markers to draw */
+static GPtrArray *
+time_markers_get (cairo_t *cr,
+                  gint64   since_usec,
+                  gint64   until_usec,
+                  gdouble  width_pixels)
+{
+        GPtrArray *markers;
+        gint64 size_usec;
+        gdouble secs_per_pixel;
+        gdouble minimum_distance_pixels;
+        gdouble secs_per_minimum_distance;
+        guint64 distance_usec;
+        gdouble distance_pixels;
+        guint64 t_usec;
+        gint64 timezone_offset_usec;
+        TextMarkerGranularity granularity;
+        gdouble margin_pixels;
+        guint n;
 
-                if (flags * GDU_CURVE_FLAGS_FILLED) {
-                        if (flags & GDU_CURVE_FLAGS_FADE_EDGES) {
-                                set_fade_edges_pattern (cr, x, x + lb_width, fill_color);
+        /* minimum distance between markers */
+        minimum_distance_pixels = 70;
+
+        /* margin on left/right of marker text */
+        margin_pixels = 3;
+
+        size_usec = until_usec - since_usec;
+
+        secs_per_pixel = size_usec / ((gdouble) G_USEC_PER_SEC) / width_pixels;
+        secs_per_minimum_distance = minimum_distance_pixels * secs_per_pixel;
+
+        markers = g_ptr_array_new_with_free_func ((GDestroyNotify) time_marker_free);
+
+        if (secs_per_minimum_distance < 60*60) {
+                /* 1 hour granularity */
+                distance_usec = 60*60L * G_USEC_PER_SEC;
+                granularity = TEXT_MARKER_GRANULARITY_1_HOUR;
+        } else if (secs_per_minimum_distance < 3*60*60) {
+                /* 3 hour granularity */
+                distance_usec = 3*60*60L * G_USEC_PER_SEC;
+                granularity = TEXT_MARKER_GRANULARITY_3_HOUR;
+        } else if (secs_per_minimum_distance < 24*60*60) {
+                /* day granularity */
+                distance_usec = 24*60*60L * G_USEC_PER_SEC;
+                granularity = TEXT_MARKER_GRANULARITY_1_DAY;
+        } else if (secs_per_minimum_distance < 7*24*60*60) {
+                /* week granularity */
+                distance_usec = 7*24*60*60L * G_USEC_PER_SEC;
+                granularity = TEXT_MARKER_GRANULARITY_1_WEEK;
+        } else if (secs_per_minimum_distance < 30*24*60*60) {
+                /* month granularity */
+                distance_usec = 30*24*60*60L * G_USEC_PER_SEC;
+                granularity = TEXT_MARKER_GRANULARITY_1_MONTH;
+        } else /* if (secs_per_minimum_distance < 365*24*60*60)*/ {
+                /* year granularity */
+                distance_usec = 365*24*60*60L * G_USEC_PER_SEC;
+                granularity = TEXT_MARKER_GRANULARITY_1_YEAR;
+        }
+        distance_pixels = distance_usec / secs_per_pixel / G_USEC_PER_SEC;
+
+        /* TODO: figure this out from somewhere */
+        timezone_offset_usec = - 4 * 60 * 60 * 1L * G_USEC_PER_SEC;
+
+        struct tm tm_now;
+        time_t time_now_utc;
+
+        time_now_utc = time (NULL);
+        localtime_r (&time_now_utc, &tm_now);
+
+        /* we use different loops according to the granularity used */
+        if (granularity < TEXT_MARKER_GRANULARITY_1_DAY) {
+                /* Now, for the distance D, we sweep over the interval [since - 2*D; until + 2*D] and
+                 * create a marker for each point T in said interval where T is divisible by D.
+                 *
+                 * We also adjust for the local timezone to get the markers to land correctly, e.g.
+                 * at 12am when using a 3-hour granularity.
+                 */
+                for (t_usec = since_usec - 2 * distance_usec - (since_usec % distance_usec) -
+                             timezone_offset_usec - 24*60*60*1L*G_USEC_PER_SEC;
+                     t_usec < until_usec + 2 * distance_usec;
+                     t_usec += distance_usec) {
+                        TimeMarker *marker;
+                        time_t time_utc_sec;
+                        struct tm tm;
+                        gchar *s;
+
+                        time_utc_sec = t_usec / G_USEC_PER_SEC;
+                        localtime_r (&time_utc_sec, &tm);
+
+                        if (tm.tm_hour == 0) {
+                                s = time_marker_format_time_day (cr, distance_pixels - 2*margin_pixels, &tm, &tm_now);
                         } else {
-                                cairo_set_source_rgba (cr,
-                                                       fill_color->red,
-                                                       fill_color->green,
-                                                       fill_color->blue,
-                                                       fill_color->alpha);
+                                s = time_marker_format_time_hour (cr, distance_pixels - 2*margin_pixels, &tm, &tm_now);
                         }
-                        cairo_rectangle (cr, x + 1, y + 1, lb_width - 2, lb_height - 2);
-                        cairo_fill (cr);
-                } else {
-                        cairo_move_to (cr, x, y + lb_height/2.0);
-                        cairo_line_to (cr, x + lb_width / 2.0, y + lb_height/3.0);
-                        cairo_line_to (cr, x + lb_width, y + 2.0 * lb_height/3.0);
 
-                        cairo_set_line_width (cr, width);
-                        cairo_set_source_rgba (cr,
-                                               color->red,
-                                               color->green,
-                                               color->blue,
-                                               color->alpha);
-                        cairo_stroke (cr);
+                        marker = g_new0 (TimeMarker, 1);
+                        marker->text = s;
+                        marker->time_usec = t_usec;
+                        marker->flags = TIME_MARKER_FLAGS_TEXT_AREA | TIME_MARKER_FLAGS_EMPHASIS;
+                        marker->text_align = TIME_MARKER_TEXT_ALIGN_LEFT;
+                        marker->text_width_pixels = distance_pixels;
+                        g_ptr_array_add (markers, marker);
+
+                        /* add extra markers */
+                        switch (granularity) {
+                        case TEXT_MARKER_GRANULARITY_1_HOUR:
+                                /* four extra markers => every 15 minutes */
+                                for (n = 0; n < 4; n++) {
+                                        marker = g_new0 (TimeMarker, 1);
+                                        marker->time_usec = t_usec + n * distance_usec / 4;
+                                        marker->flags = TIME_MARKER_FLAGS_NONE;
+                                        g_ptr_array_add (markers, marker);
+                                }
+                                break;
+                        case TEXT_MARKER_GRANULARITY_3_HOUR:
+                                /* three extra markers => every hour */
+                                for (n = 0; n < 3; n++) {
+                                        marker = g_new0 (TimeMarker, 1);
+                                        marker->time_usec = t_usec + n * distance_usec / 3;
+                                        marker->flags = TIME_MARKER_FLAGS_NONE;
+                                        g_ptr_array_add (markers, marker);
+                                }
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                                break;
+                        }
+
+                }
+        } else {
+                GDate date_iter;
+                GDate date_until;
+                GDate date_next;
+
+                g_date_clear (&date_iter, 1);
+                g_date_clear (&date_until, 1);
+                g_date_clear (&date_next, 1);
+
+                g_date_set_time_t (&date_iter,  (time_t) ((since_usec - timezone_offset_usec) / G_USEC_PER_SEC));
+                g_date_set_time_t (&date_until, (time_t) ((until_usec - timezone_offset_usec) / G_USEC_PER_SEC));
+
+                /* adjust so we start from beginning of the specified period */
+                switch (granularity) {
+                case TEXT_MARKER_GRANULARITY_1_DAY:
+                        g_date_subtract_days (&date_iter, 1);
+                        g_date_add_days (&date_until, 1);
+                        break;
+                case TEXT_MARKER_GRANULARITY_1_WEEK:
+                        g_date_add_days (&date_iter, -g_date_get_weekday (&date_iter));
+                        g_date_subtract_days (&date_iter, 7);
+                        g_date_add_days (&date_until, 7);
+                        break;
+                case TEXT_MARKER_GRANULARITY_1_MONTH:
+                        g_date_set_day (&date_iter, 1);
+                        g_date_subtract_months (&date_iter, 1);
+                        g_date_add_months (&date_until, 1);
+                        break;
+                case TEXT_MARKER_GRANULARITY_1_YEAR:
+                        g_date_set_day (&date_iter, 1);
+                        g_date_set_month (&date_iter, 1);
+                        g_date_subtract_years (&date_iter, 1);
+                        g_date_add_years (&date_until, 1);
+                        break;
+                default:
+                        g_assert_not_reached ();
+                        break;
                 }
 
-                /* and now show the text */
+                while (g_date_compare (&date_iter, &date_until) < 0) {
+                        TimeMarker *marker;
+                        gint64 t_usec;
+                        struct tm tm;
+                        gchar *s;
+
+                        g_date_to_struct_tm (&date_iter, &tm);
+                        tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+
+                        date_next = date_iter;
+                        switch (granularity) {
+                        case TEXT_MARKER_GRANULARITY_1_DAY:
+                                g_date_add_days (&date_next, 1);
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_WEEK:
+                                g_date_add_days (&date_next, 7);
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_MONTH:
+                                g_date_add_months (&date_next, 1);
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_YEAR:
+                                g_date_add_years (&date_next, 1);
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                                break;
+                        }
 
-                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
-                cairo_select_font_face (cr, "sans",
-                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
-                cairo_set_font_size (cr, 8.0);
-                cairo_text_extents (cr, text, &te);
-                cairo_move_to (cr,
-                               x + lb_width + 3 - te.x_bearing,
-                               y + lb_height/2.0 - te.height/2 - te.y_bearing);
+                        /* distance in pixels varies since each month/year isn't the same */
+                        distance_usec = g_date_days_between (&date_iter, &date_next) * 24*60*60*1L*G_USEC_PER_SEC;
+                        distance_pixels = distance_usec / secs_per_pixel / G_USEC_PER_SEC;
+
+                        switch (granularity) {
+                        case TEXT_MARKER_GRANULARITY_1_DAY:
+                                s = time_marker_format_time_day (cr, distance_pixels - 2*margin_pixels, &tm, &tm_now);
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_WEEK:
+                                s = time_marker_format_time_week (cr, distance_pixels - 2*margin_pixels, &tm, &tm_now);
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_MONTH:
+                                s = time_marker_format_time_month (cr, distance_pixels - 2*margin_pixels, &tm, &tm_now);
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_YEAR:
+                                s = time_marker_format_time_year (cr, distance_pixels - 2*margin_pixels, &tm, &tm_now);
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                                break;
+                        }
 
-                cairo_show_text (cr, text);
+#if 0
+                        g_debug ("Using `%s' for sec=%d min=%d hour=%d mday=%d mon=%d year=%d wday=%d yday=%d isdst=%d",
+                                 s,
+                                 tm.tm_sec,
+                                 tm.tm_min,
+                                 tm.tm_hour,
+                                 tm.tm_mday,
+                                 tm.tm_mon,
+                                 tm.tm_year,
+                                 tm.tm_wday,
+                                 tm.tm_yday,
+                                 tm.tm_isdst);
+#endif
+
+                        t_usec = timelocal (&tm) * 1L * G_USEC_PER_SEC;
+
+                        /* TODO: Maybe want to center some markers, like months */
+                        marker = g_new0 (TimeMarker, 1);
+                        marker->text = s;
+                        marker->time_usec = t_usec;
+                        marker->flags = TIME_MARKER_FLAGS_TEXT_AREA | TIME_MARKER_FLAGS_EMPHASIS;
+                        marker->text_align = TIME_MARKER_TEXT_ALIGN_LEFT;
+                        marker->text_width_pixels = distance_pixels;
+                        g_ptr_array_add (markers, marker);
+
+                        /* add extra markers */
+                        switch (granularity) {
+                        case TEXT_MARKER_GRANULARITY_1_DAY:
+                                /* four extra markers => every 6 hours */
+                                for (n = 0; n < 4; n++) {
+                                        marker = g_new0 (TimeMarker, 1);
+                                        marker->time_usec = t_usec + n * distance_usec / 4;
+                                        marker->flags = TIME_MARKER_FLAGS_NONE;
+                                        g_ptr_array_add (markers, marker);
+                                }
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_WEEK:
+                                /* seven extra markers => every day */
+                                for (n = 0; n < 7; n++) {
+                                        marker = g_new0 (TimeMarker, 1);
+                                        marker->time_usec = t_usec + n * distance_usec / 7;
+                                        marker->flags = TIME_MARKER_FLAGS_NONE;
+                                        g_ptr_array_add (markers, marker);
+                                }
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_MONTH:
+                                /* four extra markers => every week */
+                                for (n = 0; n < 4; n++) {
+                                        marker = g_new0 (TimeMarker, 1);
+                                        marker->time_usec = t_usec + n * distance_usec / 4;
+                                        marker->flags = TIME_MARKER_FLAGS_NONE;
+                                        g_ptr_array_add (markers, marker);
+                                }
+                                break;
+                        case TEXT_MARKER_GRANULARITY_1_YEAR:
+                                /* twelve extra markers => every month */
+                                for (n = 0; n < 12; n++) {
+                                        marker = g_new0 (TimeMarker, 1);
+                                        marker->time_usec = t_usec + n * distance_usec / 12;
+                                        marker->flags = TIME_MARKER_FLAGS_NONE;
+                                        g_ptr_array_add (markers, marker);
+                                }
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                                break;
+                        }
 
-                lb_xpos += lb_width + 3 + ceil (te.width) + 12;
+                        date_iter = date_next;
+                }
         }
 
-        /* measure text on the left y-axis */
-        left_margin = 0;
-        for (n = 0; n < n_y_markers_left; n++) {
-                twidth = ceil (measure_width (cr, graph->priv->y_markers_left[n]));
-                if (twidth > left_margin)
-                        left_margin = twidth;
-        }
-        /* include half width of first xmarker label */
-        if (n_x_markers > 0) {
-                twidth = ceil (measure_width (cr, graph->priv->x_markers[0]));
-                if (twidth/2 > left_margin)
-                        left_margin = twidth/2;
-        }
-        left_margin += 6; /* padding */
-        gx += left_margin;
-        gw -= left_margin;
+        return markers;
+}
 
-        /* measure text on the right y-axis */
-        right_margin = 0;
-        for (n = 0; n < n_y_markers_right; n++) {
-                twidth = ceil (measure_width (cr, graph->priv->y_markers_right[n]));
-                if (twidth/2 > right_margin)
-                        right_margin = twidth/2;
-        }
-        /* include half width of last xmarker label */
-        if (n_x_markers > 0) {
-                twidth = ceil (measure_width (cr, graph->priv->x_markers[n_x_markers - 1]));
-                right_margin += twidth/2;
-        }
-        right_margin += 6; /* padding */
-        gw -= right_margin;
+typedef enum {
+        TIMEBAR_STYLE_SEPARATE,
+        TIMEBAR_STYLE_EMBEDDED
+} TimeBarStyle;
 
-        /* draw the box to draw in */
-        cairo_set_source_rgb (cr, 1, 1, 1);
-        cairo_rectangle (cr, gx, gy, gw, gh);
-        cairo_set_line_width (cr, 0.0);
-	cairo_fill (cr);
-
-        /* draw markers on the left y-axis */
-        for (n = 0; n < n_y_markers_left; n++) {
-                gdouble pos;
-                gdouble dashes[1] = {2.0};
-                const gchar *s;
-                cairo_text_extents_t te;
+static void
+draw_curves (cairo_t *cr,
+             gdouble gx, gdouble gy, gdouble gw, gdouble gh,
+             TimeBarStyle timebar_style,
+             GList *curve_list,
+             gint64 window_since_usec, gint64 window_until_usec)
+{
+        GTimeVal now;
+        GList *l;
+        gint64 window_size_usec;
+        guint n;
+        gdouble x, y;
+        guint64 t_left;
+        guint64 t_right;
+        guint timebar_height;
 
-                pos = ceil (gy + gh / (n_y_markers_left - 1) * n);
+        if (timebar_style == TIMEBAR_STYLE_SEPARATE)
+                timebar_height = 12;
+        else
+                timebar_height = 0;
 
-                s = graph->priv->y_markers_left[n_y_markers_left - 1 - n];
+        window_size_usec = window_until_usec - window_since_usec;
 
-                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
-                cairo_select_font_face (cr, "sans",
-                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
-                cairo_set_font_size (cr, 8.0);
-                cairo_text_extents (cr, s, &te);
-                cairo_move_to (cr,
-                               gx/2.0 - 3 - te.width/2  - te.x_bearing,
-                               pos - te.height/2 - te.y_bearing);
+        cairo_reset_clip (cr);
 
-                cairo_show_text (cr, s);
+        /* draw the box to draw in */
+        cairo_set_source_rgb (cr, 1, 1, 1);
+        cairo_rectangle (cr, gx, gy, gw, gh - timebar_height);
+        cairo_fill_preserve (cr);
+        cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+        cairo_set_line_width (cr, 1.0);
+        cairo_set_dash (cr, NULL, 0, 0.0);
+        cairo_stroke (cr);
 
+        g_get_current_time (&now);
+        t_left = now.tv_sec - 6 * 24 * 60 * 60;
+        t_right = now.tv_sec;
+
+        /* draw time bar */
+        if (timebar_height > 0) {
+                cairo_new_path (cr);
+                cairo_rectangle (cr, gx, gy + gh - timebar_height, gw, timebar_height);
+                cairo_set_source_rgb (cr, 0x72 / 255.0, 0x9f / 255.0, 0xcf / 255.0);
+                cairo_fill_preserve (cr);
+                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
                 cairo_set_line_width (cr, 1.0);
-                cairo_set_dash (cr, dashes, 1, 0.0);
-                cairo_move_to (cr,
-                               gx - 0.5,
-                               pos - 0.5);
-                cairo_line_to (cr,
-                               gx - 0.5 + gw,
-                               pos - 0.5);
+                cairo_set_dash (cr, NULL, 0, 0.0);
                 cairo_stroke (cr);
         }
 
-        /* draw markers on the right y-axis */
-        for (n = 0; n < n_y_markers_right; n++) {
-                gdouble pos;
-                const gchar *s;
+        /* clip to the graph + timebar area */
+        cairo_rectangle (cr, gx, gy, gw, gh);
+        cairo_clip (cr);
+
+        /* ---------------------------------------------------------------------------------------------------- */
+
+        GPtrArray *time_markers;
+        time_markers = time_markers_get (cr,
+                                         window_since_usec,
+                                         window_until_usec,
+                                         gw);
+        for (n = 0; n < time_markers->len; n++) {
+                TimeMarker *marker;
                 cairo_text_extents_t te;
 
-                pos = ceil (gy + gh / (n_y_markers_right - 1) * n);
+                marker = time_markers->pdata[n];
 
-                s = graph->priv->y_markers_right[n_y_markers_right - 1 - n];
+                x = ceil (gx + gw * (marker->time_usec- window_since_usec) / window_size_usec) + 0.5;
+
+                /* draw the vertical line */
+                cairo_new_path (cr);
+                cairo_move_to (cr, x, gy);
+                if (marker->flags & TIME_MARKER_FLAGS_TEXT_AREA) {
+                        cairo_line_to (cr, x, gy + gh);
+                } else {
+                        cairo_line_to (cr, x, gy + gh - timebar_height);
+                }
+                if (marker->flags & TIME_MARKER_FLAGS_EMPHASIS) {
+                        cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+                        cairo_set_line_width (cr, 1.0);
+                } else {
+                        cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.75);
+                        cairo_set_line_width (cr, 0.5);
+                }
+                cairo_set_dash (cr, NULL, 0, 0.0);
+                cairo_stroke (cr);
 
-                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
                 cairo_select_font_face (cr, "sans",
                                         CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
                 cairo_set_font_size (cr, 8.0);
-                cairo_text_extents (cr, s, &te);
-                cairo_move_to (cr,
-                               gx + gw + right_margin/2.0 + 3 - te.width/2  - te.x_bearing,
-                               pos - te.height/2 - te.y_bearing);
+                cairo_text_extents (cr, marker->text, &te);
 
-                cairo_show_text (cr, s);
-        }
-
-        g_get_current_time (&now);
-        t_left = now.tv_sec - 6 * 24 * 60 * 60;
-        t_right = now.tv_sec;
-
-        /* draw time markers on x-axis */
-        for (n = 0; n < n_x_markers; n++) {
-                double pos;
-                guint64 val;
-                const gchar *s;
-                cairo_text_extents_t te;
-                double dashes[1] = {2.0};
+                if (timebar_style == TIMEBAR_STYLE_SEPARATE) {
+                        y = gy + gh - timebar_height/2 - 8.0/2 - te.y_bearing;
+                } else {
+                        y = gy + gh - 8.0 - 2.0 - te.y_bearing;
+                }
 
-                s = graph->priv->x_markers[n];
+                if (marker->text_align == TIME_MARKER_TEXT_ALIGN_CENTER) {
 
-                pos = ceil (gx + gw / (n_x_markers - 1) * n);
-                val = t_left + (t_right - t_left) * n / (n_x_markers - 1);
+                        cairo_move_to (cr,
+                                       x  + te.x_bearing + 3 + marker->text_width_pixels/2 - te.width/2,
+                                       y);
 
-                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
-                cairo_select_font_face (cr, "sans",
-                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
-                cairo_set_font_size (cr, 8.0);
-                cairo_text_extents (cr, s, &te);
-                cairo_move_to (cr,
-                               pos - te.width/2  - te.x_bearing,
-                               gy + gh + x_markers_height/2.0 - te.y_bearing);
+                } else if (marker->text_align == TIME_MARKER_TEXT_ALIGN_LEFT) {
 
-                cairo_show_text (cr, s);
+                        cairo_move_to (cr,
+                                       x  + te.x_bearing + 3,
+                                       y);
+                }
 
-                cairo_set_line_width (cr, 1.0);
-                cairo_set_dash (cr, dashes, 1, 0.0);
-                cairo_move_to (cr,
-                               pos - 0.5,
-                               gy - 0.5);
-                cairo_line_to (cr,
-                               pos - 0.5,
-                               gy - 0.5 + gh);
-                cairo_stroke (cr);
+                cairo_show_text (cr, marker->text);
         }
+        g_ptr_array_unref (time_markers);
 
         /* clip to the graph area */
-        cairo_rectangle (cr, gx, gy, gw, gh);
+        cairo_rectangle (cr, gx, gy, gw, gh - timebar_height);
+        cairo_reset_clip (cr);
         cairo_clip (cr);
 
         for (l = curve_list; l != NULL; l = l->next) {
@@ -638,8 +885,10 @@ gdu_graph_expose_event (GtkWidget      *widget,
                 GduColor *fill_color;
                 gdouble width;
                 GduCurveFlags flags;
-                GArray *points;
+                GArray *samples;
                 guint m;
+                gdouble sample_value_min;
+                gdouble sample_value_max;
 
                 color = gdu_curve_get_color (c);
                 fill_color = gdu_curve_get_fill_color (c);
@@ -647,48 +896,117 @@ gdu_graph_expose_event (GtkWidget      *widget,
                         fill_color = color;
                 width = gdu_curve_get_width (c);
                 flags = gdu_curve_get_flags (c);
-                points = gdu_curve_get_points (c);
+                samples = gdu_curve_get_samples (c);
+
+                /* if normalization is requested, find min/max for sample values in curve on window */
+                if (flags & GDU_CURVE_FLAGS_NORMALIZE) {
+                        sample_value_min = G_MAXDOUBLE;
+                        sample_value_max = -G_MAXDOUBLE;
+
+                        for (m = 0; m < samples->len; m++) {
+                                GduSample *sample;
+
+                                sample = &g_array_index (samples, GduSample, m);
 
+                                /* ignore break points */
+                                if (sample->time_usec == G_MAXINT64 && sample->value == G_MAXDOUBLE)
+                                        continue;
+
+                                /* ignore samples outside window */
+                                x = gx + gw * (sample->time_usec - window_since_usec) / window_size_usec;
+                                if (x < gx)
+                                        continue;
+                                if (x > gx + gw)
+                                        continue;
+
+                                if (sample->value < sample_value_min)
+                                        sample_value_min = sample->value;
+                                if (sample->value > sample_value_max)
+                                        sample_value_max = sample->value;
+                        }
+
+                        if (sample_value_max - sample_value_min < 0.001) {
+                                sample_value_min -= 1.0;
+                                sample_value_max += 1.0;
+                        }
+                } else {
+                        sample_value_min = 0.0;
+                        sample_value_max = 1.0;
+                }
+
+                /* draw the curve */
                 m = 0;
-                while (m < points->len) {
-                        guint first_point_index;
+                while (m < samples->len) {
+                        gint first_sample_index;
 
                         cairo_new_path (cr);
 
-                        first_point_index = m;
-                        for (; m < points->len; m++) {
-                                GduPoint *point;
+                        first_sample_index = -1;
+                        for (; m < samples->len; m++) {
+                                GduSample *sample;
+                                gdouble sample_value_normalized;
 
-                                point = &g_array_index (points, GduPoint, m);
+                                sample = &g_array_index (samples, GduSample, m);
 
-                                if (point->x == G_MAXDOUBLE &&
-                                    point->y == G_MAXDOUBLE) {
+                                if (sample->time_usec == G_MAXINT64 && sample->value == G_MAXDOUBLE) {
                                         m++;
                                         break;
                                 }
 
-                                x = gx + gw * point->x;
-                                y = gy + gh * (1.0f - point->y);
+                                x = gx + gw * (sample->time_usec - window_since_usec) / window_size_usec;
+
+                                if (x < gx) {
+                                        if (m + 1 < samples->len) {
+                                                GduSample *next_sample;
+                                                gdouble next_x;
+
+                                                next_sample = &g_array_index (samples, GduSample, m + 1);
+                                                next_x = gx + gw * (next_sample->time_usec - window_since_usec) / window_size_usec;
+                                                if (next_x < gx)
+                                                        continue;
+                                        } else {
+                                                continue;
+                                        }
+                                }
+
+                                sample_value_normalized = (sample->value - sample_value_min) /
+                                                          (sample_value_max - sample_value_min);
+
+                                y = gy + (gh - timebar_height) * (1.0f - sample_value_normalized);
 
                                 if (y < gy + 1.0)
                                         y = gy;
 
-                                if (y > gy + gh - 1.0)
-                                        y = gy + gh - 1.0;
+                                if (y > gy + (gh - timebar_height) - 1.0)
+                                        y = gy + (gh - timebar_height) - 1.0;
 
                                 cairo_line_to (cr, x, y);
+
+                                if (first_sample_index == -1)
+                                        first_sample_index = m;
                         }
 
+                        /* then draw the curve */
+                        cairo_set_dash (cr, NULL, 0, 0.0);
+                        cairo_set_line_width (cr, width);
+                        cairo_set_source_rgba (cr,
+                                               color->red,
+                                               color->green,
+                                               color->blue,
+                                               color->alpha);
+
+                        cairo_stroke_preserve (cr);
+
                         /* fill if requested */
-                        if (flags & GDU_CURVE_FLAGS_FILLED) {
-                                GduPoint *point;
+                        if (flags & GDU_CURVE_FLAGS_FILLED && first_sample_index != -1) {
+                                GduSample *sample;
                                 gdouble first_x;
 
                                 /* first, close the path */
-                                cairo_line_to (cr, x, gy + gh);
-                                point = &g_array_index (points, GduPoint, first_point_index);
-                                first_x = gx + gw * point->x;
-                                cairo_line_to (cr, first_x, gy + gh);
+                                cairo_line_to (cr, x, gy + (gh - timebar_height));
+                                sample = &g_array_index (samples, GduSample, first_sample_index);
+                                first_x = gx + gw * (sample->time_usec - window_since_usec) / window_size_usec;
+                                cairo_line_to (cr, first_x, gy + (gh - timebar_height));
                                 cairo_close_path (cr);
 
                                 if (flags & GDU_CURVE_FLAGS_FADE_EDGES) {
@@ -700,25 +1018,156 @@ gdu_graph_expose_event (GtkWidget      *widget,
                                                                fill_color->blue,
                                                                fill_color->alpha);
                                 }
-                                cairo_fill_preserve (cr);
+                                cairo_fill (cr);
                         }
 
-                        /* then draw the curve */
-                        cairo_set_dash (cr, NULL, 0, 0.0);
-                        cairo_set_line_width (cr, width);
-                        cairo_set_source_rgba (cr,
-                                               color->red,
-                                               color->green,
-                                               color->blue,
-                                               color->alpha);
+                } /* process more samples */
+        }
+}
+
+static gboolean
+gdu_graph_expose_event (GtkWidget      *widget,
+                        GdkEventExpose *event)
+{
+        GduGraph *graph = GDU_GRAPH (widget);
+        cairo_t *cr;
+        gdouble width, height;
+        guint left_margin;
+        guint right_margin;
+        guint top_margin;
+        guint bottom_margin;
+        gdouble gx, gy, gw, gh;
+        gint64 window_since_usec;
+        gint64 window_until_usec;
+        gint64 small_graph_window_since_usec;
+        gint64 small_graph_window_until_usec;
+        GList *curve_list;
+        GList *l;
+        gint64 now_usec;
+
+        curve_list = g_hash_table_get_values (graph->priv->curves);
+        curve_list = g_list_sort (curve_list,
+                                  (GCompareFunc) curve_z_order_sort);
+
+        width = widget->allocation.width;
+        height = widget->allocation.height;
+
+        cr = gdk_cairo_create (widget->window);
+
+        cairo_rectangle (cr,
+                         event->area.x, event->area.y,
+                         event->area.width, event->area.height);
+        cairo_clip (cr);
+
+        gx = 0.5;
+        gy = 0.5;
+        gw = width;
+        gh = height;
+
+        top_margin = 10;
+        bottom_margin = 10;
+        left_margin = 10;
+        right_margin = 10;
+
+        guint small_graph_height;
+        small_graph_height = 60;
+        bottom_margin += small_graph_height;
+
+        /* adjust drawing area */
+        gy += top_margin;
+        gh -= top_margin;
+        gh -= bottom_margin;
+        gx += left_margin;
+        gw -= left_margin;
+        gw -= right_margin;
+
+        now_usec = time (NULL) * G_USEC_PER_SEC;
+        window_until_usec = graph->priv->window_end_usec;
+        if (graph->priv->window_end_usec > now_usec)
+                window_until_usec = now_usec;
+        window_since_usec = window_until_usec - graph->priv->window_size_usec;
+
+        draw_curves (cr,
+                     gx, gy, gw, gh,
+                     TIMEBAR_STYLE_SEPARATE,
+                     curve_list,
+                     window_since_usec, window_until_usec);
+
+
+        small_graph_window_since_usec = G_MAXINT64;
+        small_graph_window_until_usec = G_MININT64;
+        for (l = curve_list; l != NULL; l = l->next) {
+                GduCurve *curve;
+                GArray *samples;
+                GduSample *sample;
+                gint64 first, last;
+
+                curve = GDU_CURVE (l->data);
+                samples = gdu_curve_get_samples (curve);
+
+                if (samples == NULL || samples->len == 0)
+                        continue;
+
+                sample = &g_array_index (samples, GduSample, 0);
+                first = sample->time_usec;
+
+                sample = &g_array_index (samples, GduSample, samples->len - 1);
+                last = sample->time_usec;
+
+                if (first != G_MAXINT64) {
+                        if (first < small_graph_window_since_usec)
+                                small_graph_window_since_usec = first;
+                }
+
+                if (last != G_MAXINT64) {
+                        if (last > small_graph_window_until_usec)
+                                small_graph_window_until_usec = last;
+                }
+        }
+        if (small_graph_window_since_usec != G_MAXINT64 && small_graph_window_until_usec != G_MININT64) {
+
+                if (small_graph_window_until_usec - small_graph_window_since_usec < 21 * 24 * 3600 * 1L * G_USEC_PER_SEC)
+                        small_graph_window_since_usec = small_graph_window_until_usec - 21 * 24 * 3600 * 1L * G_USEC_PER_SEC;
+
+                draw_curves (cr,
+                             gx, gy + gh, gw, small_graph_height,
+                             TIMEBAR_STYLE_EMBEDDED,
+                             curve_list,
+                             small_graph_window_since_usec, small_graph_window_until_usec);
+
+                gdouble zm_x0;
+                gdouble zm_x1;
+                gint64 small_window_size_usec;
+
+                small_window_size_usec = small_graph_window_until_usec - small_graph_window_since_usec;
 
-                        cairo_stroke (cr);
+                zm_x0 = gx + gw * (window_since_usec - small_graph_window_since_usec) / small_window_size_usec;
+                zm_x1 = gx + gw * (window_until_usec - small_graph_window_since_usec) / small_window_size_usec;
+
+                cairo_reset_clip (cr);
+                cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
+
+#if 1
+                cairo_rectangle (cr,
+                                 gx, gy + gh,
+                                 zm_x0 - gx, small_graph_height);
+                cairo_fill (cr);
+#endif
+
+#if 1
+                cairo_rectangle (cr,
+                                 zm_x1, gy + gh,
+                                 gx + gw - zm_x1, small_graph_height);
+                cairo_fill (cr);
+#endif
 
-                } /* process more points */
         }
 
+
         g_list_free (curve_list);
 
+        cairo_destroy (cr);
+
         /* propagate event further */
         return FALSE;
 }
diff --git a/src/gdu-gtk/gdu-graph.h b/src/gdu-gtk/gdu-graph.h
index 5db40e7..b238dd5 100644
--- a/src/gdu-gtk/gdu-graph.h
+++ b/src/gdu-gtk/gdu-graph.h
@@ -54,15 +54,13 @@ struct GduGraphClass
 
 GType       gdu_graph_get_type            (void);
 GtkWidget  *gdu_graph_new                 (void);
-gchar     **gdu_graph_get_x_markers       (GduGraph           *graph);
-gchar     **gdu_graph_get_y_markers_left  (GduGraph           *graph);
-gchar     **gdu_graph_get_y_markers_right (GduGraph           *graph);
-void        gdu_graph_set_x_markers       (GduGraph           *graph,
-                                           const gchar* const *markers);
-void        gdu_graph_set_y_markers_left  (GduGraph           *graph,
-                                           const gchar* const *markers);
-void        gdu_graph_set_y_markers_right (GduGraph           *graph,
-                                           const gchar* const *markers);
+
+gint64      gdu_graph_get_window_end_usec  (GduGraph     *graph);
+gint64      gdu_graph_get_window_size_usec (GduGraph     *graph);
+void        gdu_graph_set_window_end_usec  (GduGraph     *graph,
+                                            gint64        time_usec);
+void        gdu_graph_set_window_size_usec (GduGraph     *graph,
+                                            gint64        period_usec);
 
 GduCurve   *gdu_graph_lookup_curve        (GduGraph           *graph,
                                            const gchar        *curve_id);
diff --git a/src/gdu-gtk/gdu-gtk-enums.h b/src/gdu-gtk/gdu-gtk-enums.h
index 9c1b49c..c9b5ad5 100644
--- a/src/gdu-gtk/gdu-gtk-enums.h
+++ b/src/gdu-gtk/gdu-gtk-enums.h
@@ -28,11 +28,19 @@
 
 #include <glib-object.h>
 
-typedef enum
-{
-        GDU_CURVE_FLAGS_NONE       = 0,
-        GDU_CURVE_FLAGS_FILLED     = (1 << 0),
-        GDU_CURVE_FLAGS_FADE_EDGES = (1 << 1),
+typedef enum {
+        GDU_CURVE_FLAGS_NONE                = 0,
+        GDU_CURVE_FLAGS_FILLED              = (1 << 0),
+        GDU_CURVE_FLAGS_FADE_EDGES          = (1 << 1),
+        GDU_CURVE_FLAGS_AXIS_MARKERS_LEFT   = (1 << 2),
+        GDU_CURVE_FLAGS_AXIS_MARKERS_RIGHT  = (1 << 3),
+        GDU_CURVE_FLAGS_NORMALIZE           = (1 << 4),
 } GduCurveFlags;
 
+typedef enum {
+        GDU_CURVE_UNIT_NUMBER      = 0,
+        GDU_CURVE_UNIT_TIME        = 1,
+        GDU_CURVE_UNIT_TEMPERATURE = 2
+} GduCurveUnit;
+
 #endif /* GDU_GTK_ENUMS_H */
diff --git a/src/gdu-gtk/gdu-gtk-types.h b/src/gdu-gtk/gdu-gtk-types.h
index 2be3ac0..afa97b2 100644
--- a/src/gdu-gtk/gdu-gtk-types.h
+++ b/src/gdu-gtk/gdu-gtk-types.h
@@ -33,7 +33,7 @@
 
 G_BEGIN_DECLS
 
-typedef struct GduPoint                    GduPoint;
+typedef struct GduSample                   GduSample;
 typedef struct GduColor                    GduColor;
 typedef struct GduCurve                    GduCurve;
 typedef struct GduGraph                    GduGraph;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]