
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work 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.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//

#include "graph.hh"
#include "global_beep.hh"
#include "displays.hh"
#include <string.h>
#include "language_support.hh"


inline float graph::graph_x_value_to_pixel(double value)
{
  return (value-xmin) * (graphpanel->width-1) / (xmax-xmin);
}


inline float graph::graph_y_value_to_pixel(double value)
{
  return graphpanel->height-1 - (value-ymin) * (graphpanel->height-1) / (ymax-ymin);
}


inline int graph::graph_x_value_to_datapoint(double value)
{
  return int(floor( (value - gd->x_data_start) / gd->x_data_step));
}

bool graph::x_zoom_function(my_event& me)
{
  static double  x0_selected;
  static double  x1_selected;

  // Figure out where x touches are relative to graphpanel.  All zooms keep those points
  int x0 = me.c[0].x - graph_left_offset;
  int x1 = me.c[1].x - graph_left_offset;
  window* p = this;
  while(p)
    {
      x0 -= p->offset.x;
      x1 -= p->offset.x;
      p = p->parent;
    }
  int x = (x0+x1)/2;
  
  if(me.type == EVENT_TOUCH)
    {
      x0_selected = (xmax - xmin) / graphpanel->width * x0 + xmin;
      x1_selected = (xmax - xmin) / graphpanel->width * x1 + xmin;
    }
  else
    {
      double x_range = (x1_selected-x0_selected) * graphpanel->width / (x1-x0);

      double new_xmin = (x0_selected+x1_selected)/2 - x_range * x / graphpanel->width;
      double new_xmax = (x0_selected+x1_selected)/2 + x_range * (graphpanel->width - x) / graphpanel->width;
      
      set_xmin_xmax(new_xmin, new_xmax);
      delayed_redraw_count = 2;
    }
  
  return true;
}


void graph::allow_user_resize_x(bool allow)
{
  user_resize_x_ok = allow;
}

void graph::allow_user_resize_y(bool allow)
{
  user_resize_y_ok = allow;
}


bool graph::y_zoom_function(my_event& me)
{
  static double  y0_selected;
  static double  y1_selected;

  // Figure out where x touches are relative to graphpanel.  All zooms keep those points
  int y0 = me.c[0].y - graph_top_offset;
  int y1 = me.c[1].y - graph_top_offset;
  window* p = this;
  while(p)
    {
      y0 -= p->offset.y;
      y1 -= p->offset.y;
      p = p->parent;
    }
  int y = (y0+y1)/2;

  if(me.type == EVENT_TOUCH)
    {
      y0_selected = (ymin - ymax) / graphpanel->height * y0 + ymax;
      y1_selected = (ymin - ymax) / graphpanel->height * y1 + ymax;
    }
  else
    {
      // y and y_selected are negatives of each other, one positive up and the other positive down, so need a negative sign
      double y_range = - (y1_selected-y0_selected) * graphpanel->height / (y1-y0);

      double new_ymin = (y0_selected+y1_selected)/2 - y_range * (graphpanel->height - y) / graphpanel->height;
      double new_ymax = (y0_selected+y1_selected)/2 + y_range * y / graphpanel->height;
  
      set_ymin_ymax(new_ymin, new_ymax);
      delayed_redraw_count = 2;
    }

  return true;
}

bool graph::zoom_function(my_event& me)
{
  static bool zoom_x = false;
  static bool zoom_y = false;

  if(me.type == EVENT_HOLD)
    return true;

  int dist_x = abs(me.c[1].x - me.c[0].x);
  int dist_y = abs(me.c[1].y - me.c[0].y);

  //if(dist_x>150)
  //  zoom_x = true;
  //if(dist_y>50)
  //  zoom_y = true;
  
  if(me.type == EVENT_TOUCH)
    {
      if(dist_x==0 && dist_y==0)
	{
	  display* disp = get_display();  
	  disp->entry_rejected_beep();
	  zoom_hotspot->release_events();
	  zoom_hotspot->trash_events_until_release();
	  return true;
	}

      if(dist_x==0)
	{
	  zoom_x = false;
	  zoom_y = true;
	}
      else if(dist_y==0)
	{
	  zoom_x = true;
	  zoom_y = false;
	}
      else if(dist_x/dist_y > 1.0)
	{
	  zoom_x = true;
	  zoom_y = false;
	}
      else if(dist_y/dist_x > 1.0)
	{
	  zoom_x = false;
	  zoom_y = true;
	}
      else
	{
	  zoom_x = true;
	  zoom_y = true;
	}      


      //printf("******************************************************************\n");
      //printf("  graph got event_touch for zoom_function.  p0=(%d,%d) p1=(%d,%d)\n", me.c[0].x, me.c[0].y, me.c[1].x, me.c[1].y);
      //printf("  dist_x=%d, dist_y=%d, zoom_x=%d, zoom_y=%d\n", dist_x, dist_y, zoom_x, zoom_y);
      //printf("******************************************************************\n");

      
      zoom_hotspot->claim_events();
    }
  else if(me.type == EVENT_RELEASE)
    {
      zoom_hotspot->release_events();
      mark_layout_dirty();
    }

  if(zoom_x && user_resize_x_ok)
    x_zoom_function(me);

  if(zoom_y && user_resize_y_ok)
    y_zoom_function(me);

  return true;
}



bool graph::zoom_function_wheel(my_event& me)
{
  // Figure out where mouse was relative to graphpanel.  X zoom near X axis, Y zoom near Y axis
  int x = me.c[0].x - graph_left_offset;
  int y = me.c[0].y - graph_top_offset;
  window* p = this;
  while(p)
    {
      x -= p->offset.x;
      y -= p->offset.y;
      p = p->parent;
    }
  
  int dist_from_y_axis = min(x, graphpanel->width-x);
  int dist_from_x_axis = min(y, graphpanel->height-y);
  
  bool zoom_x = ( (x<=0)                               ? false :
		  (y>=graphpanel->height)              ? true  :
		  (dist_from_x_axis<dist_from_y_axis)  ? true  :
		  /**/                                   false );

  
  //  printf("in zoom_function_wheel, zoom_x=%d, count=%d\n", zoom_x, me.count);
  
  if(zoom_x && user_resize_x_ok)
    {
      double x_selected = (xmax - xmin) / graphpanel->width * x + xmin;

      double old_x_range = xmax - xmin;
      double x_range = pow(1.2,  me.count) * old_x_range; 

      double new_xmin = x_selected - x_range * x / graphpanel->width;
      double new_xmax = x_selected + x_range * (graphpanel->width - x) / graphpanel->width;
  
      set_xmin_xmax(new_xmin, new_xmax, false);
            
      delayed_redraw_count = 2;
    }
  else if(user_resize_y_ok)
    {
      double y_selected = (ymin - ymax) / graphpanel->height * y + ymax;

      double old_y_range = ymax - ymin;
      double y_range = pow(1.2,  me.count) * old_y_range; 

      double new_ymin = y_selected - y_range * (graphpanel->height - y) / graphpanel->height;
      double new_ymax = y_selected + y_range * y / graphpanel->height;
  
      set_ymin_ymax(new_ymin, new_ymax, false);
      
      delayed_redraw_count = 2;
    }
  return true;
}

bool graph::xaxis_move_zoom_function(hotspot* hs, my_event&me, void* data)
{  
  static double  x_selected = 0;
  graph* g = (graph*)hs->parent;

  if(!g->user_resize_x_ok)
    return true;

  if(me.type == EVENT_KEY_PRESS || me.type == EVENT_KEY_RELEASE || me.type == EVENT_KEY_HOLD)
    return false;

  if(!me.source_mouse)
    {
      if(me.num_touches==2 && me.type == EVENT_HOLD && me.count==5)
	{
	  // 2-finger autoscale x to full data width
	  g->set_xmin_xmax();
	  g->mark_layout_dirty();
	  hs->release_events();
	  hs->trash_events_until_release();
	  return true;
	}
      else if(me.num_touches==2 && me.type == EVENT_RELEASE)
	{
	  hs->release_events();
	  return true;
	}
      else if(me.type==EVENT_HOLD)
	{
	  return true;
	}
      else if(me.num_touches==2)
	{
	  if(me.type == EVENT_TOUCH)
	    hs->claim_events();
	  
	  return g->x_zoom_function(me);
	}
    }
  else if(me.source_mouse)
    {
      if(me.type == EVENT_WHEEL)
	{
	  return g->zoom_function_wheel(me);
	}
      else if(me.type == EVENT_TOUCH)
	{
	  //disp->touch_recognized_beep();
	  hs->claim_events();
	  
	  // Figure out where mouse was relative to graphpanel.  All zooms are about that point
	  int x = me.c[0].x - g->graph_left_offset;
	  window* p = g;
	  while(p)
	    {
	      x -= p->offset.x;
	      p = p->parent;
	    }
	  
	  x_selected = (g->xmax - g->xmin) / g->graphpanel->width  * x + g->xmin;
	  
	  return true;
	}
      else if(me.type == EVENT_RELEASE)
	{
	  //disp->entry_accepted_beep();
	  hs->release_events();
	  g->mark_layout_dirty();
	  return true;
	}
      else if(me.type == EVENT_HOLD && me.count==5)
	{
	  // 2-finger autoscale x to full data width
	  g->set_xmin_xmax();
	  g->mark_layout_dirty();
	  hs->release_events();
	  hs->trash_events_until_release();
	  return true;
	}
      else if(me.type == EVENT_MOVE)
	{
	  double x_range = g->xmax - g->xmin;
	  
	  // Figure out where mouse is relative to graphpanel.  Put center there
	  int x = me.c[0].x - g->graph_left_offset;
	  window* p = g;
	  while(p)
	    {
	      x -= p->offset.x;
	      p = p->parent;
	    }
	  
	  // Selected point is put at (x,y).
	  double new_xmin = x_selected - x_range * x / g->graphpanel->width;
	  double new_xmax = x_selected + x_range * (g->graphpanel->width - x) / g->graphpanel->width;

	  g->set_xmin_xmax(new_xmin, new_xmax, false);
	  
	  return true;
	}
    }

  
  return false;
}

bool graph::yaxis_move_zoom_function(hotspot* hs, my_event&me, void* data)
{
  static double  y_selected;

  graph* g = (graph*)hs->parent;

  if(!g->user_resize_y_ok)
    return true;

  if(me.type == EVENT_KEY_PRESS || me.type == EVENT_KEY_RELEASE || me.type == EVENT_KEY_HOLD)
    return false;

  if(!me.source_mouse)
    {
      if(me.num_touches==2 && me.type == EVENT_HOLD && me.count==5)
	{
	  // 2-finger autoscale y
	  g->set_ymin_ymax();
	  g->mark_layout_dirty();
	  hs->release_events();
	  hs->trash_events_until_release();
	  return true;
	}
      else if(me.num_touches==2 && me.type == EVENT_RELEASE)
	{
	  hs->release_events();
	  return true;
	}
      else if(me.num_touches==2 && me.type==EVENT_HOLD)
	{
	  return true;
	}
      else if(me.num_touches==2)
	{
	  if(me.type == EVENT_TOUCH)
	    hs->claim_events();
	  
	  return g->y_zoom_function(me);
	}
    }
  else if(me.source_mouse)
    {
      if(me.type == EVENT_WHEEL)
	{
	  return g->zoom_function_wheel(me);
	}
      else if(me.type == EVENT_TOUCH)
	{
	  //disp->touch_recognized_beep();
	  hs->claim_events();
	  
	  // Figure out where mouse was relative to graphpanel.  All zooms are about that point
	  int y = me.c[0].y - g->graph_top_offset;
	  window* p = g;
	  while(p)
	    {
	      y -= p->offset.y;
	      p = p->parent;
	    }
	  
	  y_selected = (g->ymin - g->ymax) / g->graphpanel->height * y + g->ymax;
	  
	  return true;
	}
      else if(me.type == EVENT_RELEASE)
	{
	  //disp->entry_accepted_beep();
	  hs->release_events();
	  g->mark_layout_dirty();
	  return true;
	}
      else if(me.type == EVENT_HOLD && me.count==5)
	{
	  // 2-finger autoscale y
	  g->set_ymin_ymax();
	  g->mark_layout_dirty();
	  hs->release_events();
	  hs->trash_events_until_release();
	  return true;
	}
      else if(me.type == EVENT_MOVE)
	{
	  double y_range = g->ymax - g->ymin;
	  
	  // Figure out where mouse is relative to graphpanel.  Put center there
	  int y = me.c[0].y - g->graph_top_offset;
	  window* p = g;
	  while(p)
	    {
	      y -= p->offset.y;
	      p = p->parent;
	    }
	  
	  // Selected point is put at (x,y).
	  double new_ymin = y_selected - y_range * (g->graphpanel->height - y) / g->graphpanel->height;
	  double new_ymax = y_selected + y_range * y / g->graphpanel->height;

	  g->set_ymin_ymax(new_ymin, new_ymax, false);
	  
	  return true;
	}
    }
  
  return false;
}


bool graph::move_zoom_function(hotspot* hs, my_event&me, void* data)
{
  static double  x_selected;
  static double  y_selected;
  
  graph*       g  = (graph*)hs->parent;
  display*  disp  = g->get_display();  

  if(me.type == EVENT_KEY_PRESS || me.type == EVENT_KEY_RELEASE || me.type == EVENT_KEY_HOLD)
    return false;

  if(me.num_touches==2 && me.type == EVENT_HOLD && me.count==5)
    {
      // 2-finger autoscale x and y
      g->set_xmin_xmax();
      g->set_ymin_ymax();
      g->mark_layout_dirty();
      hs->release_events();
      hs->trash_events_until_release();
      return true;
    }
  else if(me.num_touches==2 && me.type == EVENT_RELEASE)
    {
      hs->release_events();
      return true;
    }
    
  if(me.type == EVENT_WHEEL)
    return g->zoom_function_wheel(me);


  if(me.num_touches>2)
    {
      if(me.num_touches==5)
	return false;
      disp->entry_rejected_beep();
      hs->release_events();
      hs->trash_events_until_release();
      return true;
    }

  if(me.type != EVENT_TOUCH && me.type != EVENT_HOLD && me.type != EVENT_RELEASE && me.type != EVENT_MOVE)
    {
      disp->entry_rejected_beep();
      hs->release_events();
      hs->trash_events_until_release();
      return true;
    }


  if(me.num_touches==2)
    return g->zoom_function(me);

  if(me.source_mouse && me.mouse_buttons_pressed!=MOUSE_BUTTON_MIDDLE)
    {
      disp->entry_rejected_beep();
      hs->release_events();
      hs->trash_events_until_release();
      return true;
    }

  if(me.source_mouse)
    {
      if(me.type == EVENT_TOUCH)
	{
	  //disp->touch_recognized_beep();
	  hs->claim_events();
	  
	  // Figure out where mouse was relative to graphpanel.  All zooms are about that point
	  int x = me.c[0].x - g->graph_left_offset;
	  int y = me.c[0].y - g->graph_top_offset;
	  window* p = g;
	  while(p)
	    {
	      x -= p->offset.x;
	      y -= p->offset.y;
	      p = p->parent;
	    }
	  
	  x_selected = (g->xmax - g->xmin) / g->graphpanel->width  * x + g->xmin;
	  y_selected = (g->ymin - g->ymax) / g->graphpanel->height * y + g->ymax;
	  
	  return true;
	}
      else if(me.type == EVENT_RELEASE)
	{
	  //disp->entry_accepted_beep();
	  hs->release_events();
	  g->mark_layout_dirty();
	  return true;
	}
      else if(me.type == EVENT_HOLD && me.count==5)
	{
	  // 2-finger autoscale y
	  g->set_ymin_ymax();
	  g->mark_layout_dirty();
	  hs->release_events();
	  hs->trash_events_until_release();
	  return true;
	}
      else if(me.type == EVENT_MOVE)
	{
	  double x_range = g->xmax - g->xmin;
	  double y_range = g->ymax - g->ymin;
	  
	  // Figure out where mouse is relative to graphpanel.  Put center there
	  int x = me.c[0].x - g->graph_left_offset;
	  int y = me.c[0].y - g->graph_top_offset;
	  window* p = g;
	  while(p)
	    {
	      x -= p->offset.x;
	      y -= p->offset.y;
	      p = p->parent;
	    }
	  
	  // Selected point is put at (x,y).
	  double new_xmin = x_selected - x_range * x / g->graphpanel->width;
	  double new_xmax = x_selected + x_range * (g->graphpanel->width - x) / g->graphpanel->width;
	  double new_ymin = y_selected - y_range * (g->graphpanel->height - y) / g->graphpanel->height;
	  double new_ymax = y_selected + y_range * y / g->graphpanel->height;

	  if(g->user_resize_x_ok)
	    g->set_xmin_xmax(new_xmin, new_xmax, false);
	  if(g->user_resize_y_ok)
	    g->set_ymin_ymax(new_ymin, new_ymax, false);
      
	  return true;
	}
    }
  else if(me.type == EVENT_KEY_PRESS)
    {
      // Let someone else handle keys
      return false;
    }
  else
    {
      disp->entry_rejected_beep();
      hs->release_events();
      hs->trash_events_until_release();
      return true;
    }

  return true;
}



static int limit(int v, int minval, int maxval)
{
  if(v<minval)
    return minval;
  if(v>maxval)
    return maxval;

  return v;
}




void graph::draw_dynamic()
{
  // For the graphing process.  Call this before using the data
  update_data_being_graphed();

  if(!gd)
    return;

  if(delayed_redraw_count)
    {
      delayed_redraw_count--;

      if(!delayed_redraw_count)
	{
	  mark_layout_dirty();
	}
    }
  
  graphpanel->parent = this;
  

  if(gd->error_text[0])
    {
      int old_size =  global_get_text_size();

      int size = error_text_font_height;

      if(size<10)
	size=10;
      
      for(;;)
	{
	  set_text_size(size);
	  int width = calculate_text_width(gd->error_text);

	  if(width < graphpanel->width*7/8)
	    break;

	  size = size * 19 / 20;

	  if(size<10)
	    {
	      size=10;
	      break;
	    }
	}

      //graphpanel->set_text_size(size);  This is ignored here.  We're drawing directly.

      graphpanel->draw_text(gd->error_text,
			    ERROR_COLOR,
			    graphpanel->width/2,
			    graphpanel->height/2,
			    DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);
      global_set_text_size(old_size);
      return;
    }
  else
    {
      // Draw graphs
      plot_data(gd);
    }
  
 
  
#ifdef DRAW_TICS
  // Draw major xtics
  for(int i=0; i<num_x_tic_labels; i++)
    {
      int x = graph_x_value_to_pixel(x_tic_label_values[i]);      
      graphpanel->draw_line( point(x, gheight-1), point(x, gheight-1-major_tic_length), TIC_COLOR);
      graphpanel->draw_line( point(x, 0),         point(x, major_tic_length),           TIC_COLOR);
    }
    
  
  // Draw major ytics
  for(int i=0; i<num_y_tic_labels; i++)
    {
      int y = graph_y_value_to_pixel(y_tic_label_values[i]);      
      graphpanel->draw_line( point(0, y),                         point(major_tic_length, y), TIC_COLOR);
      graphpanel->draw_line( point(gwidth-1-major_tic_length, y), point(gwidth-1,         y), TIC_COLOR);
    }

  // Draw minor xtics
  for(double xf=x_tic_unlabeled_start; xf<x_tic_label_values[num_x_tic_labels-1]; xf+=x_tic_unlabeled_step)
    {
      int x = graph_x_value_to_pixel(xf);
      graphpanel->draw_line( point(x, gheight-1), point(x, gheight-1-minor_tic_length), TIC_COLOR);
      graphpanel->draw_line( point(x, 0),         point(x, minor_tic_length),           TIC_COLOR);      
    }

  
  // Draw minor ytics
  for(double yf=y_tic_unlabeled_start; yf<y_tic_label_values[num_y_tic_labels-1]; yf+=y_tic_unlabeled_step)
    {
      int y = graph_y_value_to_pixel(yf);
      graphpanel->draw_line( point(0, y),                         point(minor_tic_length,  y), TIC_COLOR);
      graphpanel->draw_line( point(gwidth-1-minor_tic_length, y), point(gwidth-1,          y), TIC_COLOR);
    }
#endif
  
  // Draw box
#ifdef DRAW_BOX_DYNAMIC
  int gwidth  = graphpanel->width;
  int gheight = graphpanel->height;
  graphpanel->draw_line( point(0, 0),           point(gwidth-1, 0),         AXIS_COLOR);  // top
  graphpanel->draw_line( point(0, gheight-1),   point(gwidth-1, gheight-1), AXIS_COLOR);  // bottom
  graphpanel->draw_line( point(0, 0),           point(0,        gheight-1), AXIS_COLOR);  // left
  graphpanel->draw_line( point(gwidth-1, 0),    point(gwidth-1, gheight-1), AXIS_COLOR);  // right
#endif
  
  multiwindow::draw_dynamic();
}

void graph::draw_dirty()
{
  if(!dirty)
    return;
  dirty = false;

  update_data_being_graphed();

  //display* disp = get_display();  printf("int graph::draw_dirty enter.  display->layout_dirty=%d\n", disp->layout_dirty); fflush(stdout);

  clear(BG_COLOR);
    
  if(!graphpanel || graphpanel->width!=graph_right_offset-graph_left_offset || graphpanel->height!=graph_bottom_offset-graph_top_offset)
    {
      if(graphpanel)
	delete graphpanel;
  
      graphpanel = new subpanel(this, point(graph_left_offset, graph_top_offset), graph_right_offset-graph_left_offset, graph_bottom_offset-graph_top_offset);
    }


  graphpanel->clear(PANEL_COLOR);

  if(!gd)
    return;

  // Draw title
  if(draw_title)
    {
      set_text_size(title_font_height);
      // The color position graph_data::MAX_DATASETS holds the starting title color.
      draw_multicolored_text(title, graph_data::MAX_DATASETS, GRAPH_COLORS, width/2, title_offset, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_BOTTOM|DRAW_TEXT_ROTATE_0);
    }
  
  // Draw X axis title
  if(draw_x_title)
    {
      set_text_size(axis_title_font_height);
      draw_text(x_axis_title, AXIS_TITLE_COLOR, width/2, x_title_offset, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_BOTTOM|DRAW_TEXT_ROTATE_0);
    }

  // Draw Y axis title
  if(draw_x_title)
    {
      set_text_size(axis_title_font_height);
      draw_text(y_axis_title, AXIS_TITLE_COLOR, y_title_offset, graph_center_y, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_BOTTOM|DRAW_TEXT_ROTATE_90_LEFT);
    }

  // Draw X tic labels
  if(draw_x_tic_labels)
    {
      set_text_size(axis_tic_label_font_height);
      for(int i=0; i<num_x_tic_labels; i++)
	if(x_tic_label_drawn[i])
	  draw_text(x_tic_label_text[i], TIC_LABEL_COLOR, graph_x_value_to_pixel(x_tic_label_values[i])+graph_left_offset, x_tic_label_offset, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_BOTTOM|DRAW_TEXT_ROTATE_0);
    }

  // Draw Y tic labels
  if(draw_y_tic_labels)
    {
      set_text_size(axis_tic_label_font_height);
      for(int i=0; i<num_y_tic_labels; i++)
	if(y_tic_label_drawn[i])
	  draw_text(y_tic_label_text[i], TIC_LABEL_COLOR, y_tic_label_offset, graph_y_value_to_pixel(y_tic_label_values[i])+graph_top_offset, DRAW_TEXT_X_RIGHT|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);
    }


  int gwidth  = graphpanel->width;
  int gheight = graphpanel->height;

  // Draw minor xgrid
  for(double xf=x_tic_unlabeled_start; xf<x_tic_label_values[num_x_tic_labels-1]; xf+=x_tic_unlabeled_step)
    {
      int x = graph_x_value_to_pixel(xf);
      graphpanel->draw_line( point(x, 0), point(x, gheight-1), GRID_MINOR_COLOR);
    }

  
  // Draw minor ygrid
  for(double yf=y_tic_unlabeled_start; yf<y_tic_label_values[num_y_tic_labels-1]; yf+=y_tic_unlabeled_step)
    {
      int y = graph_y_value_to_pixel(yf);
      graphpanel->draw_line( point(0, y), point(gwidth-1, y), GRID_MINOR_COLOR);
    }

  // Draw major xgrid
  for(int i=0; i<num_x_tic_labels; i++)
    {
      int x = graph_x_value_to_pixel(x_tic_label_values[i]);      
      graphpanel->draw_line( point(x, 0), point(x, gheight-1), GRID_MAJOR_COLOR);
    }
    
  
  // Draw major ygrid
  for(int i=0; i<num_y_tic_labels; i++)
    {
      int y = graph_y_value_to_pixel(y_tic_label_values[i]);      
      graphpanel->draw_line( point(0, y), point(gwidth-1, y), GRID_MAJOR_COLOR);
    }

  // Draw box
  graphpanel->draw_line( point(0, 0),           point(gwidth-1, 0),         AXIS_COLOR);  // top
  graphpanel->draw_line( point(0, gheight-1),   point(gwidth-1, gheight-1), AXIS_COLOR);  // bottom
  graphpanel->draw_line( point(0, 0),           point(0,        gheight-1), AXIS_COLOR);  // left
  graphpanel->draw_line( point(gwidth-1, 0),    point(gwidth-1, gheight-1), AXIS_COLOR);  // right

  //printf("window position (%d,%d), area (%d,%d)\n", offset.x, offset.y, width, height);
  //printf("graphpanel clear area (%d,%d)\n", graphpanel->width, graphpanel->height);
  //parent->parent->clear(BLUE);
  //graphpanel->clear(RED);
  //graphpanel->draw_multicolored_text("TEST", 0, GRAPH_COLORS, 100, 100, DRAW_TEXT_X_RIGHT|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);

  // Draw labels
  int fheight = dataset_label_font_height;
  set_text_size(fheight);
  int xpos = graphpanel->width - fheight;
  int j = 0;
  for(int i=0; i<gd->num_datasets; i++)
    {
      if(!labels[i][0])
	continue;
      
      int ypos = fheight*3*j/2 + fheight ;

      j++;
      
      graphpanel->draw_text(labels[i], GRAPH_COLORS.c[i], xpos, ypos, DRAW_TEXT_X_RIGHT|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);
    }

  //{ display* disp = get_display();  printf("int graph::draw_dirty exit.  display->layout_dirty=%d\n", disp->layout_dirty); fflush(stdout); }
}



//
// Calculate the next tic
//
double next_tic(double current, double step)
{
  double mult = floor((current+step/65536)/step);

  return step * (mult+1.0);
}



//
// Calculate x tic labels
//
void graph::calculate_x_tic_labels()
{
  bool   increment_unlabled_step[] = { true, false, true };
  double step_multipliers[] = { 2.0, 5.0/2.0, 10.0/5.0 };
  int   num_multipliers = sizeof(step_multipliers) / sizeof(step_multipliers[0]);
  double x_range = fabs(xmax - xmin);

  set_text_size(axis_tic_label_font_height);
  
  double step = 1.0;

  while(step>x_range)
    step /= 10.0;
  while(step<x_range)
    step *= 10.0;

  step /= 1000.0;
  int current_multiplier = 0;

  x_tic_unlabeled_step = step;
  
  // Check the current step; see if it's viable
  for(;;)
    {
      if(increment_unlabled_step[current_multiplier])
	x_tic_unlabeled_step = step;
      x_tic_unlabeled_start = next_tic(xmin, step);
      step *= step_multipliers[current_multiplier];
      current_multiplier = (current_multiplier+1) % num_multipliers;
      
      int approx_num_labels = x_range / step + 2;

      //printf("xstep is %f, approx_num_labels=%d, x_tic_unlabeled_step=%f\n", step, approx_num_labels, x_tic_unlabeled_step);

      if(approx_num_labels>MAX_TIC_LABELS-1)
	continue;

      //
      // Calculate appropriate steps
      //
      x_tic_label_values[0] = xmin;
      double current_tic = next_tic(xmin, step);
      for(num_x_tic_labels=1; current_tic<xmax; num_x_tic_labels++)
	{
	  x_tic_label_values[num_x_tic_labels] = current_tic;
	  current_tic = next_tic(current_tic, step);
	}
      x_tic_label_values[num_x_tic_labels] = xmax;
      num_x_tic_labels++;

      //
      // Quick and dirty, for now.  To do it right, should look at how much
      // space is between the labels.
      //
      for(int i=0; i<num_x_tic_labels; i++)
	x_tic_label_drawn[i] = true;

      if(num_x_tic_labels>2)
	{
	  if(x_tic_label_values[1] - xmin < 0.69*step)
	    x_tic_label_drawn[1] = false;
	  if(xmax - x_tic_label_values[num_x_tic_labels-2] < 0.69*step)
	    x_tic_label_drawn[num_x_tic_labels-2] = false;
	}
	  

      //
      // Label the steps
      //
      for(int i=0; i<num_x_tic_labels; i++)
	float2text(x_tic_label_values[i], x_tic_label_text[i]);
      
      //
      // Calculate the max and total width
      //
      x_tic_label_greatest_width = 0;
      for(int i=0; i<num_x_tic_labels; i++)
	{
	  int width = calculate_text_width(x_tic_label_text[i]);
	  
	  if(width>x_tic_label_greatest_width)
	    x_tic_label_greatest_width = width;
	}

      int label_max_total_width = x_tic_label_greatest_width * num_x_tic_labels;

      // If the labels are too wide, try again
      if(label_max_total_width <= 3*width/4)
	break;
    }  
}


//
// Calculate y tic labels
//
void graph::calculate_y_tic_labels()
{
  bool   increment_unlabeled_step[] = { true, false, true };
  double step_multipliers[] = { 2.0, 5.0/2.0, 10.0/5.0 };
  int   num_multipliers = sizeof(step_multipliers) / sizeof(step_multipliers[0]);
  double y_range = fabs(ymax - ymin);

  double step = 1.0;

  set_text_size(axis_tic_label_font_height);
  
  while(step<y_range)
    step *= 10.0;

  step /= 1000.0;
  int current_multiplier = 0;
  
  y_tic_unlabeled_step = step;

  // Check the current step; see if it's viable
  for(;;)
    {
      if(increment_unlabeled_step[current_multiplier])
	y_tic_unlabeled_step = step;
      y_tic_unlabeled_start = next_tic(ymin, step);
      step *= step_multipliers[current_multiplier];
      current_multiplier = (current_multiplier+1) % num_multipliers;
      
      int approx_num_labels = y_range / step + 2;

      //printf("ystep is %f, approx_num_labels=%d\n", step, approx_num_labels);
      
      if(approx_num_labels==2)
	break;
      
      if(approx_num_labels>MAX_TIC_LABELS-1)
	continue;

      int label_total_height = axis_tic_label_font_height * approx_num_labels;

      //printf("  label_total_height=%d, height=%d\n", label_total_height, height);
      
      if(label_total_height < height/2)
	break;
    }

  //
  // Calculate appropriate steps
  //
  y_tic_label_values[0] = ymin;
  double current_tic = next_tic(ymin, step);
  for(num_y_tic_labels=1; current_tic<ymax; num_y_tic_labels++)
    {
      y_tic_label_values[num_y_tic_labels] = current_tic;
      current_tic = next_tic(current_tic, step);
    }
  y_tic_label_values[num_y_tic_labels] = ymax;
  num_y_tic_labels++;

  //
  // Quick and dirty, for now.  To do it right, should look at how much
  // space is between the labels.
  //
  for(int i=0; i<num_y_tic_labels; i++)
    y_tic_label_drawn[i] = true;

  if(num_y_tic_labels>2)
    {
      if(y_tic_label_values[1] - ymin < 0.697*step)
	y_tic_label_drawn[1] = false;
      if(ymax - y_tic_label_values[num_y_tic_labels-2] < 0.697*step)
	y_tic_label_drawn[num_y_tic_labels-2] = false;
    }

  //
  // Label the steps
  //
  for(int i=0; i<num_y_tic_labels; i++)
    float2text(y_tic_label_values[i], y_tic_label_text[i]);

  //
  // Calculate the max width
  //
  y_tic_label_greatest_width = 0;
  for(int i=0; i<num_y_tic_labels; i++)
    {
      int width = calculate_text_width(y_tic_label_text[i]);

      if(width>y_tic_label_greatest_width)
	y_tic_label_greatest_width = width;
    }
}


//
// Calculate the graph geometry.  If space becomes tight, drop titles then labels until there's some reasonable graph space.
//
void graph::layout()
{
  update_data_being_graphed();
  
  //printf("in graph::do_layout()  width=%d, height=%d\n", width, height); fflush(stdout);
  draw_title          = (title[0]!=0);
  draw_x_title        = (x_axis_title[0]!=0);
  draw_y_title        = (y_axis_title[0]!=0);
  draw_x_tic_labels   = true;
  draw_y_tic_labels   = true;

  const int min_x_graph_width = 50;
  const int min_y_graph_width = 50;
    
  // font sizes
  title_font_height             = limit(height * title_font_percent/100, title_font_min, title_font_max);

  while(title_font_height>2)
    {
      set_text_size(title_font_height);
      int twidth = calculate_text_width(title);
      if(twidth<width*15/16)
	break;
      title_font_height--;
    }

  axis_title_font_height        = limit(height * axis_title_font_percent/100, axis_title_font_min, axis_title_font_max);

  while(axis_title_font_height>2)
    {
      set_text_size(axis_title_font_height);
      int twidth = calculate_text_width(y_axis_title);
      if(twidth<(height-3*axis_title_font_height)*7/8)
	break;
      axis_title_font_height--;
    }

  axis_tic_label_font_height    = limit(height * axis_tic_label_font_percent/100, axis_tic_label_font_min, axis_tic_label_font_max);

  dataset_label_font_height    = limit(height * dataset_label_font_percent/100, dataset_label_font_min, dataset_label_font_max);

  error_text_font_height    = limit(height * error_text_font_percent/100, error_text_font_min, error_text_font_max);
  

  //printf("title_font_height=%d, axis_title_font_height=%d, axis_tic_label_font_height=%d\n", title_font_height, axis_title_font_height, axis_tic_label_font_height);

  //printf("   graph::do_layout() recalculate x_tic labels\n"); fflush(stdout);

  // Figure out how we want to label the x and y axes
  calculate_x_tic_labels();


  //printf("   graph::do_layout() recalculate y_tic labels\n"); fflush(stdout);
  calculate_y_tic_labels();


  minor_tic_length              = limit(width * 2/100, 3, 8);
  major_tic_length              = limit(width * 4/100, 6, 16);
  
 recalculate_widths:

  //printf("   graph::do_layout() recalculate widths\n"); fflush(stdout);

  graph_x_label_margin          = draw_x_tic_labels ? x_tic_label_greatest_width / 2 : 0;

  
  // Widths, from left to right.  "width" or "margin" indicate how wide an item is.  "offset" indicates position from left edge of panel (0).

  graph_left_margin             = limit(width * 2/100, 5, 10);
  y_title_width                 = draw_y_title ? axis_title_font_height : 0;
  y_title_offset                = graph_left_margin + y_title_width;                        // Offset of y-axis title text, which is specified from a right center point, with text rotated 90 degrees (center bottom text).
  y_title_right_margin          = draw_y_title ? limit(width * 4/100, 5, 10) : 0;
  y_tic_label_width             = draw_y_tic_labels ? y_tic_label_greatest_width : 0;
  y_tic_label_offset            = y_title_offset + y_title_right_margin + y_tic_label_width;  // Offset of y-axis tic text, which is specified from a right center point.
  y_tic_label_margin            = draw_y_tic_labels ? limit(width * 2/100, 5, 10) : 0;
  graph_left_offset             = y_tic_label_offset + y_tic_label_margin;

  if(graph_left_offset<graph_x_label_margin+graph_left_margin)
    graph_left_offset = graph_x_label_margin + graph_left_margin;
  
  // Widths, from right to left.

  graph_right_margin            = limit(width * 4/100, 5, 10);
  graph_right_offset            = width - graph_right_margin - graph_x_label_margin;

  // Cancel the y title on the left side, if room is too tight.  If it's even tighter, don't label the y axis at all.
  if(graph_right_offset - graph_left_offset < min_x_graph_width)
    {
      if(draw_y_title)
	{
	  draw_y_title = false;
	  goto recalculate_widths;
	}
      if(draw_y_tic_labels)
	{
	  draw_y_tic_labels = false;
	  goto recalculate_widths;
	}
    }

 recalculate_heights:

  //printf("   graph::do_layout() recalculate heights\n"); fflush(stdout);

  // Heights, from top to bottom.  "height" indicates how tall an item is.  "offset' indicates position from top (0)

  graph_top_margin             = limit(height * 3/100, 10, 20);
  title_height                  = draw_title ? title_font_height : 0;
  title_offset                  = graph_top_margin + title_height;
  title_below_margin            = draw_title ? limit(height * 3/100, 10, 20) : 0;
  graph_top_offset              = title_offset + title_below_margin;

  // Heights, from bottom to top.

  graph_bottom_margin           = limit(height * 3/100, 10, 20);
  x_title_offset                = height - graph_bottom_margin;                            // Offset of x-axis title text, which is specified from a top center point.
  x_title_height                = draw_x_title ? axis_title_font_height : 0;
  x_title_above_margin          = draw_x_title ? limit(height * 3/100, 10, 20) : 0;
  x_tic_label_offset            = x_title_offset - x_title_height - x_title_above_margin;
  x_tic_label_height            = draw_x_tic_labels ? axis_tic_label_font_height : 0;      // Same as axis_tic_label_font_height
  x_tic_above_margin            = draw_x_tic_labels ? limit(height * 3/100, 10, 20) : 0;
  graph_bottom_offset           = x_tic_label_offset - x_tic_label_height - x_tic_above_margin;

  // Cancel the title, if room is too tight.  If it's even tighter, don't label the x axis at all.
  if(graph_bottom_offset - graph_top_offset < min_y_graph_width)
    {
      if(draw_title)
	{
	  draw_title = false;
	  goto recalculate_heights;
	}
      if(draw_x_title)
	{
	  draw_x_title = false;
	  goto recalculate_heights;
	}
      if(draw_x_tic_labels)
	{
	  draw_x_tic_labels = false;
	  goto recalculate_heights;
	}
    }

  
  // Centers
  
  graph_center_x = ( graph_right_offset + graph_left_offset   ) / 2;
  graph_center_y = ( graph_top_offset   + graph_bottom_offset ) / 2;


  //printf("   graph::do_layout() resizing hotspot\n"); fflush(stdout);


  if(zoom_hotspot)
    {
      zoom_hotspot->resize(graph_left_offset, graph_top_offset,        // offset 
			   graph_right_offset - graph_left_offset,     // width
			   graph_bottom_offset - graph_top_offset);    // height

      zoom_hotspot->layout();
    }

  if(xaxis_hotspot)
    {
      xaxis_hotspot->resize(graph_left_offset, graph_bottom_offset,    // offset 
			   graph_right_offset - graph_left_offset,     // width
			   height - graph_bottom_offset);              // height

      xaxis_hotspot->layout();
    }

  if(yaxis_hotspot)
    {
      yaxis_hotspot->resize(0, graph_top_offset,                       // offset 
			    graph_left_offset,                         // width
			    graph_bottom_offset - graph_top_offset);   // height

      yaxis_hotspot->layout();
    }


  layout_dirty = false;
  dirty = true;

  //printf("leaving graph::do_layout()\n"); fflush(stdout);
}


graph::graph() : multiwindow(GREY3)
{
  delayed_redraw_count = 0;

  ymin_in_last_dataset = -1;
  ymax_in_last_dataset =  1;

  user_resize_x_ok = true;
  user_resize_y_ok = true;
  
  last_num_datasets = 0;
  
  gd     = 0;
  new_gd = 0;
  old_gd = 0;

  title_font_percent = 4;
  title_font_min     = 30;
  title_font_max     = 1000;

  axis_title_font_percent = 3;
  axis_title_font_min     = 25;
  axis_title_font_max     = 1000;

  axis_tic_label_font_percent = 2;
  axis_tic_label_font_min     = 20;
  axis_tic_label_font_max     = 1000;

  dataset_label_font_percent  = 2;
  dataset_label_font_min      = 15;
  dataset_label_font_max      = 1000;

  error_text_font_percent = 6;
  error_text_font_min     = 50;
  error_text_font_max     = 1000;

  xmin = 0;
  xmax = 0;
  ymin = 0;
  ymax = 0;

  TITLE_COLOR      = WHITE;
  AXIS_TITLE_COLOR = WHITE;
  TIC_LABEL_COLOR  = GREY9;
  AXIS_COLOR       = GREY8;
  TIC_COLOR        = GREY9;
  BG_COLOR         = GREY3;
  PANEL_COLOR      = GREY2;
  GRID_MINOR_COLOR = GREY5;
  GRID_MAJOR_COLOR = GREY7;
  ERROR_COLOR      = REDRED9;

  graphpanel = 0;

  //for(int g=0; g<graph_data::MAX_DATASETS; g++)
  //  {
  //    char temp[400];
  //    sprintf(temp, "test %d", g);
  //    set_label(g, temp);
  //  }

  color colors[graph_data::MAX_DATASETS] = { RED, GREEN, BLUE, ORANGE, BLUEGREEN, PURPLE, YELLOW, GREY, WHITE };
  
  for(int i=0; i<graph_data::MAX_DATASETS; i++)
    GRAPH_COLORS.c[i] = colors[i];
  
  GRAPH_COLORS.c[graph_data::MAX_DATASETS] = TITLE_COLOR;
  
  draw_title    = false;
  draw_x_title  = false;
  draw_y_title  = false;

  zoom_hotspot  = new hotspot (graph::move_zoom_function, 0);
  xaxis_hotspot = new hotspot (graph::xaxis_move_zoom_function, 0, false);
  yaxis_hotspot = new hotspot (graph::yaxis_move_zoom_function, 0, false);

  add(zoom_hotspot);
  add(xaxis_hotspot);
  add(yaxis_hotspot);
}

graph::~graph()
{
  if(graphpanel)
    delete graphpanel;

  delete zoom_hotspot;
  delete xaxis_hotspot;
  delete yaxis_hotspot;

  delete gd;
}

//
// Round xmin and xmax slightly to make them display nicely.
// Since these are then the actual values used when graphing,
// this doesn't affect graphing accuracy.
//
//
// returns true if values are accepted without significant
// alteration.  Otherwise, returns false.
//
bool graph::set_xmin_xmax(double new_xmin, double new_xmax, bool force)
{
  bool retvalue = true;

  double x_data_last  = gd->x_data_start + (gd->num_points-1) * gd->x_data_step;
  double x_data_difference = x_data_last - gd->x_data_start;

  if(new_xmin==0 && new_xmax==0)
    {
      new_xmin = gd->x_data_start;
      new_xmax = x_data_last;
    }
  
  double new_x_range       = new_xmax - new_xmin;
  double new_x_center      = 0.5 * (new_xmin + new_xmax);
  
  // Fix bad values for range
  if(new_x_range==0)
    {
      new_x_range = 1;
      new_xmin = new_x_center - new_x_range/2;
      new_xmax = new_x_center + new_x_range/2;
      retvalue = false;
    }
  else if(new_x_range<0)
    {
      new_x_range = -new_x_range;
      new_xmin = new_x_center - new_x_range/2;
      new_xmax = new_x_center + new_x_range/2;
      retvalue = false;
    }
      
  // Set limit on range
  if(new_x_range < 20*gd->x_data_step)
    {
      new_x_range = 20 * gd->x_data_step;
      new_xmin = new_x_center - new_x_range/2;
      new_xmax = new_x_center + new_x_range/2;
      retvalue = false;
    }

  if(new_x_range > 3.0 * x_data_difference)
    {
      new_xmin = new_x_center - 1.5*x_data_difference;
      new_xmax = new_x_center + 1.5*x_data_difference;
      new_x_range       = new_xmax - new_xmin;
      retvalue = false;
    }

      
  // Don't allow it to go off the display too much
  if(gd->x_data_start > new_xmax - 0.1 * new_x_range)
    {
      // order is important
      new_xmin += gd->x_data_start + 0.1 * new_x_range - new_xmax;
      new_xmax += gd->x_data_start + 0.1 * new_x_range - new_xmax;
      retvalue = false;
    }
  if(x_data_last < new_xmin + 0.1 * new_x_range)
    {
      // order is important
      new_xmax += x_data_last - 0.1 * new_x_range - new_xmin;
      new_xmin += x_data_last - 0.1 * new_x_range - new_xmin;
      retvalue = false;
    }


  if(retvalue==false && !force)
    return retvalue;
  
  double step = 1.0;
  
  while(step>new_x_range)
    step /= 10.0;
  while(step<new_x_range)
    step *= 10.0;
  
  step /= 1000.0;

  //printf("point a, new_xmin=%f, new_xmax=%f, step=%f\n", new_xmin, new_xmax, step);
  
  xmin = step * floor((new_xmin+step/2)/step);
  xmax = step * floor((new_xmax+step/2)/step);
    
  return retvalue;
}


//
// Round ymin and ymax slightly to make them display nicely.
// Since these are then the actual values used when graphing,
// this doesn't affect graphing accuracy.
//
// returns true if values are accepted without significant
// alteration.  Otherwise, returns false.
//
bool graph::set_ymin_ymax(double new_ymin, double new_ymax, bool force)
{
  bool retvalue = true;

  //printf("set_ymin_ymax(%f,%f)  ", new_ymin, new_ymax);
  
  //
  // Allow viewing off the ends, which helps make graph
  // movement natural.  But don't overdo it.
  //
  if(new_ymin==0 && new_ymax==0)
    {
      double ymin_in_dataset;
      double ymax_in_dataset;
      gd->find_ymin_ymax(ymin_in_dataset, ymax_in_dataset);
      double new_y_range = ymax_in_dataset - ymin_in_dataset;
      double new_y_center = 0.5 * (ymax_in_dataset + ymin_in_dataset);
      new_ymax = new_y_center + 0.6 * new_y_range;
      new_ymin = new_y_center - 0.6 * new_y_range;
    }

  double y_data_difference = ymax_in_last_dataset - ymin_in_last_dataset;
  double new_y_range       = new_ymax - new_ymin;
  double new_y_center      = 0.5 * (new_ymin + new_ymax);

  // Fix bad values for range
  if(new_y_range==0)
    {
      new_y_range = 1;
      new_ymin = new_y_center - new_y_range/2;
      new_ymax = new_y_center + new_y_range/2;
      retvalue = false;
    }
  else if(new_y_range<0)
    {
      new_y_range = -new_y_range;
      new_ymin = new_y_center - new_y_range/2;
      new_ymax = new_y_center + new_y_range/2;
      retvalue = false;
    }


  // Set limits on range
  if(new_y_range > 10.0*y_data_difference)
    {
      new_y_range = 10 * y_data_difference;
      new_ymin = new_y_center - new_y_range/2;
      new_ymax = new_y_center + new_y_range/2;
      retvalue = false;
    }
  else if(new_y_range < 0.05*y_data_difference)
    {
      new_y_range = 0.05*y_data_difference;
      new_ymin = new_y_center - new_y_range/2;
      new_ymax = new_y_center + new_y_range/2;
      retvalue = false;
    }

  // Limit range relative to center value.
  // This keeps from zeroing in to a large
  // center value to arbirary precision.
  if(new_y_range < 0.00001 * fabs(new_y_center))
    {
      new_y_range = 0.00001 * fabs(new_y_center);
      new_ymin = new_y_center - new_y_range/2;
      new_ymax = new_y_center + new_y_range/2;
      retvalue = false;
    }

  
  // Don't allow it to go off the display too much
  if(new_ymax < ymin_in_last_dataset + 0.1 * new_y_range)
    {
      // order is important
      new_ymin += ymin_in_last_dataset + 0.1 * new_y_range - new_ymax;
      new_ymax += ymin_in_last_dataset + 0.1 * new_y_range - new_ymax;
      retvalue = false;
    }
  if(new_ymin > ymax_in_last_dataset - 0.1 * new_y_range)
    {
      // order is important
      new_ymax += ymax_in_last_dataset - 0.1 * new_y_range - new_ymin;
      new_ymin += ymax_in_last_dataset - 0.1 * new_y_range - new_ymin;
      retvalue = false;
    }


  if(retvalue==false && !force)
    return retvalue;
  
  double step = 1.0;

  while(step>new_y_range)
    step /= 10.0;
  while(step<new_y_range)
    step *= 10.0;

  step /= 1000.0;

  ymin = step * floor((new_ymin+step/2)/step);
  ymax = step * floor((new_ymax+step/2)/step);

  //printf("  set to (%f,%f) last_dataset=(%f,%f)\n", ymin, ymax, ymin_in_last_dataset, ymax_in_last_dataset);

  return retvalue;
}


#define MAX_PLOT_DATA_POINTS 2000  // Should be around 2000-8000


// update_data_being_graphed() is to be called only by the graphing process,
// just before graphing.  If a new_gd is available, move it into position.
// Flag that we're ready for another new gd by zeroing new_gd.
void graph::update_data_being_graphed()
{
  if(new_gd)
    {
      old_gd  = gd;
      gd      = new_gd;
      new_gd = 0;
    }
  
  if(!old_gd)
    {
      set_xmin_xmax();
      set_ymin_ymax();
      ymin=-1; ymax =1;
      mark_layout_dirty();
    }

  if(gd->num_datasets != last_num_datasets)
    {
      // If the number of datasets change and we're drawing labels, need to redraw the labels.
      last_num_datasets = gd->num_datasets;
      mark_dirty();
    }
}



void graph::plot_data(graph_data* gd)
{
  static struct data_point y_data_min_max[MAX_PLOT_DATA_POINTS];
  
  if(!gd)
    return;
  
  //
  // Updating data is now done through update_data, which is on an entirely different function
  // call tree than plotting data.  This is to separate data updates from screen updates, so
  // that data averaging doesn't change immensely on different screens.
  //
  //update_y_data();

  int firstx = graph_x_value_to_datapoint(xmin) - 1;

  // 16 is because trigger variance can mean 8 samples of jitter in graph positioning if
  // PPC is 8 or 10 if PPC is 10.  So 16 covers it.
  int lastx  = graph_x_value_to_datapoint(xmax) + 1 + 16;

  firstx = firstx&~1;  // Code below requires firstx be even
    
  if(firstx<0)
    firstx=0;
  if(lastx>=gd->num_points)
    lastx=gd->num_points-1;

  if(firstx>gd->num_points-1 || lastx<0 || lastx<=firstx)
    return;

  display* d = get_display();

  int num_min_max_points = lastx - firstx + 1;
  int min_max_points_per_point = 1;
  while(num_min_max_points>MAX_PLOT_DATA_POINTS)
    {
      num_min_max_points /= 2;
      min_max_points_per_point *= 2;
    }
  

  int x_offset = graphpanel->offset.x;
  int y_offset = graphpanel->offset.y;
  window* p = this;
  while(p)
    {
      x_offset += p->offset.x;
      y_offset += p->offset.y;
      p = p->parent;
    }

  double ymin_in_dataset = -1;  // Overridden later, if there's any data
  double ymax_in_dataset =  1;
  bool   first_max       = true;

  for(int graphitem=0; graphitem<gd->num_datasets; graphitem++)
    {
      int16_t color = GRAPH_COLORS.c[graphitem];

      int min_max_point = 0;
      int min_max_phase = 0;
      bool first_point = true;
      y_data_min_max[0].min_y = NAN;
      y_data_min_max[0].max_y = NAN;
      
      for(int pnt=firstx; pnt<=lastx; pnt++)
	{
	  double y = gd->y_data[graphitem]->data[pnt];

	  //if(pnt<=10)
	  //  {
	  //    printf("y_data[%d]=%f  ", pnt, y);
	  //    if(pnt==10)
	  //	printf("\n");
	  //  }
	  
	  if(isfinite(y))
	    {
	      if(first_point)
		{
		  first_point = false;
		  y_data_min_max[min_max_point].min_y = y;
		  y_data_min_max[min_max_point].max_y = y;
		}
	      else
		{
		  y_data_min_max[min_max_point].min_y = (y<y_data_min_max[min_max_point].min_y) ? y : y_data_min_max[min_max_point].min_y;
		  y_data_min_max[min_max_point].max_y = (y>y_data_min_max[min_max_point].max_y) ? y : y_data_min_max[min_max_point].max_y;
		}

	      if(first_max)
		{
		  first_max = false;
		  ymin_in_dataset = y;
		  ymax_in_dataset = y;
		}
	      if(y<ymin_in_dataset)
		ymin_in_dataset = y;
	      if(y>ymax_in_dataset)
		ymax_in_dataset = y;
	    }

	  min_max_phase++;
	  if(min_max_phase==min_max_points_per_point)
	    {
	      min_max_phase = 0;
	      first_point = true;
	      min_max_point++;
	      y_data_min_max[min_max_point].min_y = NAN;
	      y_data_min_max[min_max_point].max_y = NAN;
	    }
	}

      // x start and step in screen coordinates
      //float x_start = g->graph_x_value_to_pixel(x_data_start + firstx * x_data_step);
      //float x_step  = g->graph_x_value_to_pixel(x_data_start + (firstx+1) * x_data_step) - x_start;

      //printf("ymm[0]=%f, ymm[1]=%f, ymm[2]=%f, ymm[3]=%f, ymm[4]=%f, ymm[5]=%f, ymm[6]=%f, ymm[7]=%f, ymm[8]=%f, ymm[9]=%f, ymm[10]=%f\n",
      //     y_data_min_max[0].max_y, y_data_min_max[1].max_y, y_data_min_max[2].max_y, y_data_min_max[3].max_y, y_data_min_max[4].max_y, y_data_min_max[5].max_y, y_data_min_max[6].max_y, y_data_min_max[7].max_y, y_data_min_max[8].max_y, y_data_min_max[9].max_y, y_data_min_max[10].max_y);
      
      d->draw_graph_data(color,                                          // color
			 min_max_point,                                  // num_points
			 gd->x_data_start + firstx * gd->x_data_step,    // x_start
			 gd->x_data_step * min_max_points_per_point,     // x_step
			 xmin,                                           // x_at_left
			 xmax,                                           // x_at_right
			 ymin,                                           // y_at_bottom
			 ymax,                                           // y_at_top
			 x_offset,                                       // x offset on display
			 y_offset,                                       // y offset on display
			 graphpanel->width,                              // width on display
			 graphpanel->height,                             // height on display
			 y_data_min_max);                                // (min,max) data for each x

    }

  if(ymin_in_dataset==ymax_in_dataset)
    {
      ymin_in_dataset -= 0.5;
      ymax_in_dataset += 0.5;
    }
  
  ymin_in_last_dataset = ymin_in_dataset;
  ymax_in_last_dataset = ymax_in_dataset;  
}


void graph::set_dataset_color(int dataset, color c)
{
  GRAPH_COLORS.c[dataset] = c;
}

void graph::set_title_color(color c)
{
  TITLE_COLOR = c;
  GRAPH_COLORS.c[graph_data::MAX_DATASETS] = TITLE_COLOR;
}

void graph::set_background_color(color c)
{
  BG_COLOR = c;
  bgcolor  = c;
}

void graph::set_tic_label_color    (color c) { TIC_LABEL_COLOR  = c; }
void graph::set_tic_color          (color c) { TIC_COLOR        = c; }
void graph::set_axis_color         (color c) { AXIS_COLOR       = c; }
void graph::set_axis_title_color   (color c) { AXIS_TITLE_COLOR = c; }
void graph::set_panel_color        (color c) { PANEL_COLOR      = c; }
void graph::set_grid_major_color   (color c) { GRID_MAJOR_COLOR = c; }
void graph::set_grid_minor_color   (color c) { GRID_MINOR_COLOR = c; }
void graph::set_error_color        (color c) { ERROR_COLOR      = c; }

void         graph::accept_data(graph_data* gd)  { new_gd = gd;     }
bool         graph::ready_for_new_data()         { return !new_gd;  }
graph_data*  graph::get_old_data()               { return old_gd;   }  


void graph::set_title_font_height(int percent, int min_pixels, int max_pixels)
{
  title_font_percent = percent;
  title_font_min     = min_pixels;
  title_font_max     = max_pixels;
}

void graph::set_axis_title_font_height(int percent, int min_pixels, int max_pixels)
{
  axis_title_font_percent = percent;
  axis_title_font_min     = min_pixels;
  axis_title_font_max     = max_pixels;
}

void graph::set_tic_label_font_height(int percent, int min_pixels, int max_pixels)
{
  axis_tic_label_font_percent = percent;
  axis_tic_label_font_min     = min_pixels;
  axis_tic_label_font_max     = max_pixels;
}

void graph::set_dataset_label_font_height(int percent, int min_pixels, int max_pixels)
{
  dataset_label_font_percent  = percent;
  dataset_label_font_min      = min_pixels;
  dataset_label_font_max      = max_pixels;
}

void graph::set_error_text_font_height(int percent, int min_pixels, int max_pixels)
{
  error_text_font_percent = percent;
  error_text_font_min     = min_pixels;
  error_text_font_max     = max_pixels;
}
