Logo Search packages:      
Sourcecode: ygraph version File versions  Download package

plot_area.c

Go to the documentation of this file.
/**
 * @file    plot_area.c
 * @brief   Plot area manipulators.
 *
 *          This file contains routines for drawing lines in the
 *          plot area.
 *
 * @author  Denis Pollney
 * @date    1 Oct 2001
 *
 * @par Copyright (C) 2001-2002 Denis Pollney
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 * @par
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details
 * @par
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <string.h>
#include <stdlib.h>

#include "ygraph.h"

/**
 * @brief    Draw a grid of lines on the plot background at the location of the
 *           axis tick marks.
 *
 * @param    plot    The Plot on which the grid is to be drawn.
 * @param    pixmap  The backing pixmap holding the image.
 */
void
00041 grid_draw(Plot* plot, GdkPixmap* pixmap)
{
  GdkGC* gc;
  GdkColor* color;
  Axis* axis;
  Tick* tick;
  guint i;
  gint i_pos;
  gint j_pos;

  gc = gdk_gc_new(pixmap);

  color = color_set_rgb(plot->colormap, global_grid_intensity,
                        global_grid_intensity, global_grid_intensity);
  
  gdk_gc_set_foreground(gc, color);
  g_free(color);

  /*
   * Draw the vertical lines.
   */
  axis = plot->x_axis;

  for (i=0; i<axis->ticks->len; ++i)
    {
      tick = g_array_index(axis->ticks, Tick*, i);
      i_pos = x_to_i(plot, tick->val);

      gdk_draw_line(pixmap, gc, i_pos, 0, i_pos, plot->j_size);
    }

  /*
   * Draw the horizontal lines.
   */
  axis = plot->y_axis;

  for (i=0; i<axis->ticks->len; ++i)
    {
      tick = g_array_index(axis->ticks, Tick*, i);
      j_pos = plot->j_size + y_to_j(plot, tick->val);

      gdk_draw_line(pixmap, gc, 0, j_pos, plot->i_size, j_pos);
    }
  gdk_gc_unref(gc);
}

/**
 * @brief    Draw range values in the corners of the plot pixmap.
 *
 * @param    plot    The Plot on which the ranges are to be drawn.
 * @param    pixmap  The backing pixmap holding the frame.
 */
void
00094 range_draw(Plot* plot)
{
  gchar range_str_ul[RANGE_STR_MAX_SIZE];
  gchar range_str_lr[RANGE_STR_MAX_SIZE];
  gint width_lr;

  g_snprintf(range_str_ul, RANGE_STR_MAX_SIZE, "(%g,%g)",
             plot->x_range[0], plot->y_range[1]);
  g_snprintf(range_str_lr, RANGE_STR_MAX_SIZE, "(%g,%g)",
             plot->x_range[1], plot->y_range[0]);

  width_lr = gdk_text_width(plot->font, range_str_lr, strlen(range_str_lr));

  gdk_draw_text
    (plot->pixmap, plot->font,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     plot->i_origin + LEGEND_WIDTH_PADDING, 
     plot->j_origin - plot->j_size + plot->font_height + LEGEND_HEIGHT_PADDING,
     range_str_ul, strlen(range_str_ul));

  gdk_draw_text
    (plot->pixmap, plot->font,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     plot->i_origin + plot->i_size - width_lr - LEGEND_WIDTH_PADDING, 
     plot->j_origin - LEGEND_HEIGHT_PADDING,
     range_str_lr, strlen(range_str_lr));
}

/**
 * @brief    Draw a line of data. If the draw_points option has been set for
 *           the given plot, then put a small rectangle at each data point.
 *
 * @param    pixmap       The backing pixmap on which the line is to be drawn.
 * @param    gc           The graphics context for the line.
 * @param    ij_data      A list of (i,j) pixel coordinates.
 * @param    npoints      The number of points in the line.
 * @param    draw_points  TRUE if dots are to be put at each data value.
 */
void
00133 plot_area_draw_data(GdkPixmap* pixmap, GdkGC* gc, GdkPoint* ij_data,
                    gint npoints, gboolean draw_points)
{
  gint i;

  g_assert(gc != NULL);
  gdk_draw_lines(pixmap, gc, ij_data, npoints);
  
  if (draw_points)
    for(i=0; i<npoints; ++i)
      gdk_draw_rectangle(pixmap, gc, TRUE, 
                         ij_data[i].x-DOT_SIZE/2, ij_data[i].y-DOT_SIZE/2,
                         DOT_SIZE, DOT_SIZE);
}

/**
 * @brief Draw all frames of all datasets in a plot window (SHOW_ALL_MODE).
 *
 * @param  plot  The plot whose frames are being drawn.
 */
void
00154 plot_window_draw_all(Plot* plot)
{
  GtkWidget* plot_area;
  GdkPixmap* data_area_pixmap;
  GdkGC* gc;
  PlotLine* plot_set;
  DataSet* data_set;
  guint i;
  gint j;

  plot_area = plot->plot_area;
  if (plot->gc == NULL)
    plot->gc = gdk_gc_new(plot_area->window);
  g_assert(plot->gc != NULL);

  /*
   * Create a new pixmap and wipe it clean with a white rectangle. The
   * pixmap is the size of the data area, ie. between the axes, so that
   * anything that gets drawn here won't erase anything in the margins
   * where the axes, legend, etc. are place. The pixmap will later get
   * copied onto the main plot area pixmap and erased.
   */
  data_area_pixmap = gdk_pixmap_new(plot_area->window, plot->i_size+1,
                                    plot->j_size+1, -1);
  gdk_draw_rectangle(data_area_pixmap, plot_area->style->white_gc, TRUE,
                     0, 0, plot->i_size+1, plot->j_size+1);

  if (plot->draw_grid)
    grid_draw(plot, data_area_pixmap);

  /*
   * Loop over all of the datasets.
   */
  for (i=0; i<plot->plot_data->len; ++i)
    {
      data_set = plot_get_data_index(plot, i);
      plot_set = g_array_index(plot->plot_data, PlotLine*, i);

      /*
       * Loop over the frames in the dataset.
       */
      for (j=0; j<data_set->nframes; ++j)
        {
          gc = line_set_gc(plot, plot_set, j, data_set->nframes);
          gdk_gc_set_line_attributes(gc, option_line_width, GDK_LINE_SOLID,
                                     GDK_CAP_BUTT, GDK_JOIN_MITER);
          gdk_draw_lines(data_area_pixmap, gc, plot_set[j].ij_data,
                         plot_set->npoints);
          plot_area_draw_data(data_area_pixmap, gc, plot_set[j].ij_data,
                              plot_set->npoints, plot->draw_points);
          gdk_gc_unref(gc);
        }
    }

  /*
   * Copy the data area pixmap onto the plot area pixmap so that when the
   * plot area is exposed, the data area will be updated.
   */
  gdk_draw_pixmap(plot->pixmap,
                  plot_area->style->fg_gc[GTK_WIDGET_STATE(plot_area)],
                  data_area_pixmap,
                  0, 0, plot->i_origin, plot->j_origin-plot->j_size,
                  -1, -1);

  if (plot->draw_range)
    range_draw(plot);

  if (plot->legend != NULL)
    legend_draw(plot);

  if (data_area_pixmap != NULL)
    gdk_pixmap_unref(data_area_pixmap);
}

/**
 * @brief    Draw the current frame for all datasets in a plot window
 *           (ANIMATE_MODE or PAUSE_MODE).
 *
 * @param    plot  Plot whose frame is being drawn.
 */
void
00235 plot_window_draw_current_frame(Plot* plot)
{
  GtkWidget* plot_area;
  GdkGC* gc;
  GdkPixmap* data_area_pixmap;
  PlotLine* plot_set;
  DataSet* data_set;
  gdouble cur_time;
  gdouble cur_time_approx;
  gint cur_data_frame;
  gint next_data_frame;
  guint i;

  plot_area = plot->plot_area;
  gc = gdk_gc_new(plot_area->window);

  /*
   * See above for an explanation of this pixmap.
   */
  data_area_pixmap = gdk_pixmap_new(plot_area->window, plot->i_size+1,
                                    plot->j_size+1, -1);
  gdk_draw_rectangle(data_area_pixmap, plot_area->style->white_gc, TRUE,
                     0, 0, plot->i_size+1, plot->j_size+1);

  if (plot->draw_grid)
    grid_draw(plot, data_area_pixmap);

  /*
   * Add a bit to the current time, in case times differ due to roundoff error.
   */
  if (global_time_list)
    cur_time = g_array_index(global_time_list, gdouble, global_current_frame);
  else
    cur_time = 0.0;
  cur_time_approx = cur_time + TIME_EPSILON;

  /*
   * Loop over the datasets in the plot window.
   */
  for (i=0; i<plot->plot_data->len; ++i)
    {
      data_set = plot_get_data_index(plot, i);
      plot_set = g_array_index(plot->plot_data, PlotLine*, i);

      /*
       * Decide which frame to draw. The last value used has been stored
       * in the plot->current_frame array, and this is used as a starting
       * guess. The time value of the next frame is checked against the
       * global current time.
       */

      /*
       * If the current frame time is greater than the global time, then
       * step back.
       */
      cur_data_frame = g_array_index(plot->current_frame, gint, i);
      while ((cur_data_frame >= 0) &&
             (plot_set[cur_data_frame].frame->time > cur_time_approx))
        --cur_data_frame;

      /*
       * If the next frame is earlier than the global
       * time, then it is the one to be plotted. Generally, the loops
       * below find the frame with time closest to, but below, the current
       * time.
       */
      next_data_frame = cur_data_frame + 1;
      while ((next_data_frame < data_set->nframes) &&
             (plot_set[next_data_frame].frame->time <= cur_time_approx))
        ++next_data_frame;

      if (next_data_frame > 0)
        cur_data_frame = next_data_frame - 1;
      else
        cur_data_frame = 0;

      array_index_set_val(plot->current_frame, gint, i, cur_data_frame);

      /*
       * There is no gradual change of colour during animation,
       * ie. set the colour to the dataset's start colour, regardless
       * of the frame number, then draw the line.
       */
      gdk_gc_set_foreground(gc, plot_set->start_color);
      gdk_gc_set_line_attributes(gc, option_line_width, GDK_LINE_SOLID,
                                 GDK_CAP_BUTT, GDK_JOIN_MITER);
      plot_area_draw_data(data_area_pixmap, gc,
                          plot_set[cur_data_frame].ij_data,
                          plot_set->npoints, plot->draw_points);
    }

  gdk_draw_pixmap(plot->pixmap,
                  plot_area->style->fg_gc[GTK_WIDGET_STATE(plot_area)],
                  data_area_pixmap,
                  0, 0, plot->i_origin, plot->j_origin-plot->j_size, -1, -1);

  if (plot->draw_range)
    range_draw(plot);

  if (plot->legend != NULL)
    legend_draw(plot);

  if (data_area_pixmap != NULL)
    gdk_pixmap_unref(data_area_pixmap);

  gdk_gc_unref(gc);
}

/**
 * @brief    Draw a time indicator in the upper left-hand corner of the plot.
 *
 * @param    plot  The Plot where the time indicator should be drawn.
 */
void
00349 plot_window_draw_time(Plot* plot)
{
  gchar time_str[TIME_STR_MAX_SIZE];

  if (global_time_list == NULL)
    return;

  /*
   * In SHOW_ALL mode, display the time range.
   */
  if (global_display_mode == SHOW_ALL_MODE)
    g_snprintf(time_str, TIME_STR_MAX_SIZE, "time = %g ... %g",
               g_array_index(global_time_list, gdouble, 0),
               g_array_index(global_time_list, gdouble, global_last_frame));
  /*
   * Otherwise only a single frame is being displayed, so show the time of the
   * current frame.
   */
  else
    g_snprintf(time_str, TIME_STR_MAX_SIZE, "time = %g",
               g_array_index(global_time_list, gdouble, global_current_frame));

  gdk_draw_rectangle(plot->pixmap, plot->plot_area->style->white_gc, TRUE,
                     plot->i_origin, 0, plot->i_origin+plot->i_size,
                     plot->j_origin-plot->j_size);

  gdk_draw_text
    (plot->pixmap, plot->font,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     plot->i_origin + LEGEND_WIDTH_PADDING, 
     plot->j_origin - plot->j_size - LEGEND_HEIGHT_PADDING,
     time_str, strlen(time_str));
}

/**
 * @brief    Draw the contents of the plot area, between the axes.
 *
 * @param    plot  The plot.
 */
void
00389 plot_area_draw(Plot* plot)
{
  if (global_display_mode == SHOW_ALL_MODE)
    plot_window_draw_all(plot);
  else
    plot_window_draw_current_frame(plot);
}

/**
 * @brief    Draw the plot.
 *
 *           Redraws the data region, inside the axes. Note that the pixmap is
 *           drawn including the top margin - that's because this is where the
 *           time is displayed, so this area needs to be redrawn with each
 *           frame update.
 *
 * @param    plot  The plot to be drawn.
 *
 * @note     Maybe there should be a separate display function
 *           for this, but for the time being, it's not a problem.
 */
void
00411 plot_window_display_data(Plot* plot)
{
  gdk_draw_pixmap
    (plot->plot_area->window,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     plot->pixmap,
     plot->i_origin, 0,
     plot->i_origin, 0,
     plot->i_size+1, plot->j_origin+1);
}

/**
 * @brief    Redraw the contents of all plot windows.
 */
void
00426 all_windows_draw(void)
{
  Plot* plot;
  guint i;

  if (global_plot_window!=NULL)
    for (i=0; i<global_plot_window->len; ++i)
      {
        plot = g_array_index(global_plot_window, Plot*, i);
        legend_draw(plot);
        plot_area_draw(plot);
        plot_window_draw_time(plot);
        plot_window_display_all(plot);
      }
}

/**
 * @brief    Move the animation to the next frame.
 *
 *           In ANIMATE_MODE, increment the frame number and draw the new
 *           frame, and switch the label on the play button if needed.
 *
 * @param    play_button  Pointer to the play button widget.
 * @returns  TRUE if successful -- could be a boolean, I guess.
 */
gint
00452 frame_draw_next(GtkObject* play_button)
{
  if (global_display_mode != ANIMATE_MODE)
    return FALSE;

  /*
   * If we're at the last frame, switch to PAUSE_MODE.
   */
  if (global_current_frame == global_last_frame)
    {
      global_display_mode = PAUSE_MODE;
      if (play_button != NULL)
        gtk_object_set(play_button, "GtkButton::label",
                       (gchar*) PLAY_BUTTON_LABEL, NULL);
      return FALSE;
    }

  current_frame_increment(1);
  current_time_display_value();

  all_windows_draw();

  return TRUE;
}

/**
 * @brief    Create a popup containing the pointer location.
 *
 * @param    plot    The Plot on which the grid is to be drawn.
 * @param    pixmap  The backing pixmap holding the image.
 */
inline void
00484 plot_area_write_location(Plot* plot, gint i, gint j)
{
  gchar location_str[RANGE_STR_MAX_SIZE];
  gint len;
  gdouble x;
  gdouble y;
  gint text_height;
  gint text_width;
  gint text_x;
  gint text_y;
  
  x = i_to_x(plot, i);
  y = j_to_y(plot, j);

  g_snprintf(location_str, RANGE_STR_MAX_SIZE, "(%g,%g)", x, y);
  len = strlen(location_str);

  text_height = gdk_text_height(plot->font, location_str, len);
  text_width = gdk_text_width(plot->font, location_str, len);
  text_x = i + 2*CROSS_SIZE;
  text_y = j - 2*CROSS_SIZE;

  if ((text_x + text_width) > (plot->i_origin + plot->i_size))
    text_x = plot->i_origin + plot->i_size - text_width;

  gdk_draw_line
    (plot->plot_area->window,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     i - CROSS_SIZE, j, i + CROSS_SIZE, j);
  gdk_draw_line
    (plot->plot_area->window,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     i, j - CROSS_SIZE, i, j + CROSS_SIZE);

  gdk_draw_text
    (plot->plot_area->window,
     plot->font,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     text_x, text_y, location_str, len);
}

/**
 * @brief    Record the point at which the left-button was clicked on the plot
 *           window so that we can start a zoom process.
 *
 * @param    plot  The Plot where the pointer is.
 * @param    x     The x coordinate (pixels) of the pointer.
 * @param    y     The y coordinate (pixels) of the pointer.
 */
inline void
00534 plot_area_zoom_start(Plot* plot, gint x, gint y)
{
  plot->zoom_x_start = x;
  plot->zoom_y_start = y;
}

/**
 * @brief    When the left-button is released, perform the zoom by resetting
 *           the ranges on the axes and redrawing the window.
 *
 * @param    plot  The Plot to be zoomed.
 * @param    x     The x coordinate (pixels) of the final point.
 * @param    y     The y coordinate (pixels) of the final point.
 */
void
00549 plot_area_zoom_finish(Plot* plot, gint x, gint y)
{
  Point_Double tmp;
  zoom_x_range_set(plot, plot->zoom_x_start, x);
  zoom_y_range_set(plot, plot->zoom_y_start, y);

  /*
   * Flag the zoom starting point as being uninitialised to prevent spurious
   * zooms.
   */
  plot->zoom_x_start = NO_ZOOM;
  plot->zoom_y_start = NO_ZOOM;

  plot->fixed_range = TRUE;

  if (!plot->zoom_history_start)
    plot->zoom_history_start=g_array_new(FALSE, FALSE, sizeof(Point_Double));
  tmp.x = plot->x_range[0];
  tmp.y = plot->y_range[0];
  g_array_append_val(plot->zoom_history_start, tmp);
  if (!plot->zoom_history_end)
    plot->zoom_history_end=g_array_new(FALSE, FALSE, sizeof(Point_Double));
  tmp.x = plot->x_range[1];
  tmp.y = plot->y_range[1];
  g_array_append_val(plot->zoom_history_end, tmp);

  plot_axes_create(plot);
  plot_window_reconfigure(plot);

  plot_window_display_all(plot);
}

/**
 * @brief    On motion events, draw a rectangle between the zoom starting
 *           point and the current cursor location in order to delimit the
 *           zoom region.
 *
 * @param    plot  The Plot.
 * @param    x     The x coordinate (pixels) of the pointer.
 * @param    y     The y coordinate (pixels) of the pointer.
 */
void
00591 plot_area_zoom_rectangle_draw(Plot* plot, gint x, gint y)
{
  GdkPixmap* pixmap;
  gint x_start;
  gint y_start;
  gint width;
  gint height;

  x_start = MIN(plot->zoom_x_start, x);
  y_start = MIN(plot->zoom_y_start, y);

  width = abs(plot->zoom_x_start - x);
  height = abs(plot->zoom_y_start - y);

  /*
   * Create a pixmap of appropriate size.
   */
  pixmap = gdk_pixmap_new(plot->plot_area->window, width+1, height+1, -1);

  /*
   * Draw the current plot area contents onto the pixmap as a background.
   */
  gdk_draw_pixmap
    (pixmap, plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     plot->pixmap, x_start, y_start, 0, 0, width, height);

  /*
   * Draw the zoom rectangle.
   */
  gdk_draw_rectangle(pixmap, plot->plot_area->style->black_gc, FALSE,
                     0, 0, width, height);

  /*
   * Re-draw the plot area window to erase any previous zoom actions.
   */
  gdk_draw_pixmap
    (plot->plot_area->window,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     plot->pixmap, 0, 0, 0, 0, -1, -1);

  /*
   * Copy the zoom pixmap onto the main plot area pixmap.
   */
  gdk_draw_pixmap
    (plot->plot_area->window,
     plot->plot_area->style->fg_gc[GTK_WIDGET_STATE(plot->plot_area)],
     pixmap, 0, 0, x_start, y_start, -1, -1);

  /* 
   * Get rid of the zoom pixmap since we don't need it any more.
   */
  if (pixmap != NULL)
    gdk_pixmap_unref(pixmap);
}

Generated by  Doxygen 1.6.0   Back to index