/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     graphXO_2d.c                                                   */
/*                                                                          */
/*                                                                          */
/* description:  simple graphical routines in 2d                            */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Universitaet Bremen                                          */
/*             Bibliothekstr. 2                                             */
/*             D-28359 Bremen, Germany                                      */
/*                                                                          */
/*             Kunibert G. Siebert                                          */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/****************************************************************************/
static const REAL_B vertex_lambda_2d[N_VERTICES_2D] = {
  INIT_BARY_2D(1.0,0.0,0.0),
  INIT_BARY_2D(0.0,1.0,0.0),
  INIT_BARY_2D(0.0,0.0,1.0),
};

#if 0
static const REAL_B midedge_lambda[N_VERTICES_2D] = {
  INIT_BARY_2D(0.0,0.5,0.5),
  INIT_BARY_2D(0.5,0.0,0.5),
  INIT_BARY_2D(0.5,0.5,0.0)
};
#endif

typedef struct min_max {
  float xmin_2d[DIM_OF_WORLD], xmax_2d[DIM_OF_WORLD], diam_2d[DIM_OF_WORLD];
} MIN_MAX;

static void xminmax_fct_2d(const EL_INFO *elinfo, void *data)
{
  MIN_MAX *ud = (MIN_MAX *)data;
  int i, j;
  PARAMETRIC *parametric = elinfo->mesh->parametric;

  if (parametric)
  {
    REAL_D world[N_VERTICES_2D];
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D,
			       vertex_lambda_2d, world);

    for (i = 0; i < N_VERTICES_2D; i++) {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	ud->xmin_2d[j] = MIN(ud->xmin_2d[j], world[i][j]);
	ud->xmax_2d[j] = MAX(ud->xmax_2d[j], world[i][j]);
      }
    }
  }
  else {
    DEBUG_TEST_FLAG(FILL_COORDS, elinfo);

    for (i = 0; i < N_VERTICES_2D; i++)
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	ud->xmin_2d[j] = MIN(ud->xmin_2d[j], elinfo->coord[i][j]);
	ud->xmax_2d[j] = MAX(ud->xmax_2d[j], elinfo->coord[i][j]);
      }    
  }
  return;
}


static GRAPH_WINDOW graph_open_window_2d(const char *title,
					 const char *geometry,
					 REAL *world, MESH *mesh)
{
  FUNCNAME("graph_open_window_2d");
  MIN_MAX    mm[1] = {{{0}}};
  OGL_WINDOW *winO = NULL;
  char       Geometry[16];
  int        i;
 
  if (world)
  {
    mm->xmin_2d[0] = world[0];
    mm->xmax_2d[0] = world[1];
    mm->xmin_2d[1] = world[2];
    mm->xmax_2d[1] = world[3];
    mm->diam_2d[0] = MAX(mm->xmax_2d[0] - mm->xmin_2d[0], 1.E-10);
    mm->diam_2d[1] = MAX(mm->xmax_2d[1] - mm->xmin_2d[1], 1.E-10);
  }
  else if (mesh)
  {
    for (i=0; i<DIM_OF_WORLD; i++)
      mm->xmax_2d[i] = -(mm->xmin_2d[i] = 1.0E10);
    mesh_traverse(mesh, -1, CALL_LEAF_EL|FILL_COORDS, xminmax_fct_2d, mm);
    for (i=0; i<DIM_OF_WORLD; i++)
    {
      mm->diam_2d[i] = MAX(mm->xmax_2d[i] - mm->xmin_2d[i], 1.E-10);
      mm->xmin_2d[i] -= 0.1 * mm->diam_2d[i];
      mm->xmax_2d[i] += 0.1 * mm->diam_2d[i];
      mm->diam_2d[i] *= 1.2;
    }
  }
  else
  {
    for (i=0; i<DIM_OF_WORLD; i++)
    {
      mm->xmin_2d[i] = 0.0;
      mm->xmax_2d[i] = 1.0;
      mm->diam_2d[i] = 1.0;
    }
  }

  if (!title)
    title = "ALBERTA graphics";

  if (!geometry)
  {
    REAL SIZE = 400.0, xsize, ysize;

    if (mm->diam_2d[0] >= mm->diam_2d[1])
    {
      xsize = SIZE;
      ysize = SIZE * mm->diam_2d[1] / mm->diam_2d[0];
    }
    else
    {
      xsize = SIZE * mm->diam_2d[0] / mm->diam_2d[1];
      ysize = SIZE;
    }
    snprintf(Geometry, 16, "%dx%d", (int)xsize, (int)ysize);
    geometry = Geometry;
    MSG("use geometry: %s\n", geometry);
  }

  winO = OGL_create_window(title, geometry);

  TEST_EXIT(winO, "Could not create window!\n");

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  for (i = 0; i < DIM_OF_WORLD; i++) {
    winO->xmin[i] = mm->xmin_2d[i];
    winO->xmax[i] = mm->xmax_2d[i];
  }
  glOrtho(winO->xmin[0], winO->xmax[0],
	  winO->xmin[1], winO->xmax[1],
	  -1.0, 1.0);
  
  graph_clear_window((GRAPH_WINDOW) winO, rgb_white);

  return((GRAPH_WINDOW) winO);
}

/****************************************************************************/

static FLAGS graph_mesh_flags_2d;
static const float *linecolor_2d;

#if HAVE_LIBGL
static void OGLgraph_mesh_fct_2d(const EL_INFO *elinfo, void *data)
{
  EL       *el = elinfo->el;
  int      i, j, i1,i2;
  double   xmid[DIM_OF_WORLD];
  DOF      **dof;
  char     cindex[20];
  REAL_D   world[N_VERTICES_2D];
  const REAL_D *coord;
  PARAMETRIC *parametric = elinfo->mesh->parametric;

  if (parametric) 
  {
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D,
			       vertex_lambda_2d, world);
    coord = (const REAL_D *)world;
  }
  else 
  {
    DEBUG_TEST_FLAG(FILL_COORDS, elinfo);
    coord = (const REAL_D *)elinfo->coord;
  }

  for (j=0; j<DIM_OF_WORLD;j++) {
    xmid[j] = 0.0;
    for (i=0;i<N_VERTICES_2D;i++) xmid[j] += coord[i][j];
    xmid[j] /= N_VERTICES_2D;
  }

  /* show refinement/coarsening mark */
  if (graph_mesh_flags_2d & GRAPH_MESH_ELEMENT_MARK)
  {
    if      (el->mark > 0) glColor3fv(rgb_red);
    else if (el->mark < 0) glColor3fv(rgb_blue);
    else                   glColor3fv(rgb_white);

    glBegin(GL_TRIANGLES);
    glVertex2dv(coord[0]);
    glVertex2dv(coord[1]);
    glVertex2dv(coord[2]);
    glEnd();
  }


#if DEBUG
  /* show element indices */
  if (graph_mesh_flags_2d & GRAPH_MESH_ELEMENT_INDEX)
  {
    if (elinfo->level < 5) {
      sprintf(cindex,"%d",el->index);
      /* PLOT_text2d(xmid[0],xmid[1],cindex); */
    }
  }
#endif


  if (graph_mesh_flags_2d & GRAPH_MESH_BOUNDARY)
  {
    /* show boundary edges */
    DEBUG_TEST_FLAG(FILL_BOUND, elinfo);

    for (i=0; i < N_WALLS_2D; i++) {
      if (!IS_INTERIOR(wall_bound(elinfo, i))) {
	if (linecolor_2d)
	  glColor3fv(linecolor_2d);
	else if (IS_DIRICHLET(wall_bound(elinfo, i)))
	  glColor3fv(rgb_blue);
	else
	  glColor3fv(rgb_red);

	i1 = (i+1) % 3;
	i2 = (i+2) % 3;
	
	glBegin(GL_LINE_STRIP);
	glVertex2dv(coord[i1]);
	/* if (parametric) insert additional points... */
	glVertex2dv(coord[i2]);
	glEnd();
      }
    }
  }
  else
  {
    /* show all edges */
    glColor3fv(linecolor_2d ? linecolor_2d : rgb_black);
    glBegin(GL_LINE_LOOP);
    for (i=0; i < N_VERTICES_2D; i++) {
      glVertex2dv(coord[i]);
      /* if (parametric) insert additional points... */
    }
    glEnd();
  }

  /* show dof[0] at vertices */
  if (graph_mesh_flags_2d & GRAPH_MESH_VERTEX_DOF)
  {
    if ((dof = el->dof)) 
    {
      for (i=0; i<N_VERTICES_2D; i++) 
      {
	if (dof[i]) sprintf(cindex,"%d",dof[i][0]);
      }
    }
  }
  return;
}
#endif


static void graph_mesh_2d(GRAPH_WINDOW win, MESH *mesh,
			  const GRAPH_RGBCOLOR c, FLAGS flags)
{
  OGL_WINDOW *ogl_win = (OGL_WINDOW *) win;

  if (!mesh) return;

  linecolor_2d = c;
  graph_mesh_flags_2d = flags;

  OGL_set_std_window(ogl_win);
  
  glLineWidth(1.0);
  
  mesh_traverse(mesh, -1, CALL_LEAF_EL | FILL_COORDS | FILL_BOUND,
		OGLgraph_mesh_fct_2d, NULL);
  OGL_FLUSH(ogl_win);

  return;
}

/****************************************************************************/

static void  graph_close_window_2d(GRAPH_WINDOW win)
{
  OGL_destroy_window((OGL_WINDOW *)win);

  return;
}

static void  graph_clear_window_2d(GRAPH_WINDOW win, const GRAPH_RGBCOLOR c)
{
  OGL_clear_window((OGL_WINDOW *)win, c ? c : rgb_white);

  return;
}

/****************************************************************************/
/****************************************************************************/

static REAL level_value = 0.0;
static int  nrefine = 0;

static const DOF_REAL_VEC   *drv  = NULL;
static const DOF_REAL_D_VEC *drdv  = NULL;

static const BAS_FCTS *bas_fcts = NULL;
static int n_bas_fcts     = 0;
static const BAS_FCT *phi = NULL;
static const REAL    *el_vec = NULL;
static const REAL_D  *el_vec_d = NULL;
static PARAMETRIC    *el_parametric = NULL;
static const EL_INFO *el_info = NULL;
static REAL val_min, val_max;

/****************************************************************************/
/* display of error estimators                                              */
/****************************************************************************/

static REAL (*Get_el_est)(EL *el) = NULL;

static REAL el_est_fct(const EL_INFO *elinfo, const REAL *lambda)
{
  return(Get_el_est(elinfo->el));
}

static void graph_el_est_2d(GRAPH_WINDOW win, MESH *mesh,
			    REAL (*get_el_est)(EL *el),
			    REAL min, REAL max)
{
  FUNCNAME("graph_el_est_2d");

  TEST_EXIT(Get_el_est = get_el_est, "no get_el_est()\n");

  graph_fvalues_2d(win, mesh, el_est_fct, 0, min, max, 0);
  MSG("values in range [%.3le, %.3le]\n", val_min, val_max);
}

static void graph_level_recursive(int refine, const REAL *b[N_VERTICES_2D],
				  REAL v[N_VERTICES_2D], const REAL *x[N_VERTICES_2D])
{
  int    i,j;

  if (refine > 0)       /* refine and call recursively */
  {
    const REAL *bnew[N_VERTICES_2D], *xnew[N_VERTICES_2D];
    REAL_B      bm[N_VERTICES_2D];
    REAL vnew[N_VERTICES_2D], vm[N_VERTICES_2D], xm[N_VERTICES_2D][DIM_OF_WORLD];

    for (j = 0; j < 3; j++)
    {
      bm[0][j] = 0.5*(b[1][j] + b[2][j]);
      bm[1][j] = 0.5*(b[0][j] + b[2][j]);
      bm[2][j] = 0.5*(b[0][j] + b[1][j]);
    }

    for (i = 0; i < 3; i++)
    {
      vm[i] = 0.0;
      for (j=0; j<n_bas_fcts; j++)
	vm[i] += el_vec[j] * PHI(bas_fcts, j, bm[i]); /* !!! phi[j](bm[i]); */
    }

    if (el_parametric)
    {
      el_parametric->coord_to_world(el_info, NULL, N_EDGES_2D,
				    (const REAL_B *)bm, xm);
    }
    else
    {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	xm[0][j] = 0.5*(x[1][j] + x[2][j]);
	xm[1][j] = 0.5*(x[0][j] + x[2][j]);
	xm[2][j] = 0.5*(x[0][j] + x[1][j]);
      }
    }

    bnew[0] = b[0]; bnew[1] = bm[2]; bnew[2] = bm[1]; 
    xnew[0] = x[0]; xnew[1] = xm[2]; xnew[2] = xm[1]; 
    vnew[0] = v[0]; vnew[1] = vm[2]; vnew[2] = vm[1]; 
    graph_level_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[2]; bnew[2] = bm[1]; 
    xnew[0] = xm[2]; xnew[1] = x[2]; xnew[2] = xm[1]; 
    vnew[0] = vm[2]; vnew[1] = v[2]; vnew[2] = vm[1]; 
    graph_level_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = b[2]; bnew[1] = bm[2]; bnew[2] = bm[0]; 
    xnew[0] = x[2]; xnew[1] = xm[2]; xnew[2] = xm[0]; 
    vnew[0] = v[2]; vnew[1] = vm[2]; vnew[2] = vm[0]; 
    graph_level_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[1]; bnew[2] = bm[0]; 
    xnew[0] = xm[2]; xnew[1] = x[1]; xnew[2] = xm[0]; 
    vnew[0] = vm[2]; vnew[1] = v[1]; vnew[2] = vm[0]; 
    graph_level_recursive(refine-1, bnew, vnew, xnew);

    return;
  }

  /* draw level line */

  {
    static REAL small = 1.0E-8;
    int   i1, i2, flag[3];
    REAL  s[3];
    float xy[2];

    for (i=0; i<3; i++) {
      i1 = (i+1) % 3;
      if (ABS(v[i1]-v[i]) >= small) {
	s[i] = (level_value - v[i]) / (v[i1]-v[i]);
	flag[i] = (s[i] <= 1.0 && s[i] >= 0.0);
      }
      else {
	flag[i] = 0;
	if (ABS(level_value - v[i]) <= small) {
	  glBegin(GL_LINE_STRIP);
	  xy[0] = x[i][0];
	  xy[1] = x[i][1];
	  glVertex2fv(xy);
	  xy[0] = x[i1][0];
	  xy[1] = x[i1][1];
	  glVertex2fv(xy);
	  glEnd();
	}
      }
    }

    for (i=0; i<3; i++) {
      i1 = (i+1) % 3;
      if (flag[i] && flag[i1]) {
	i2 = (i+2) % 3;

	glBegin(GL_LINE_STRIP);
	for (j=0; j<2; j++)
	  xy[j] = x[i][j] + s[i] * (x[i1][j] - x[i][j]);
	glVertex2fv(xy);
	for (j=0; j<2; j++)
	  xy[j] = x[i1][j] + s[i1] * (x[i2][j] - x[i1][j]);
	glVertex2fv(xy);
	glEnd();
      }
    }
  }
}

static void graph_level_fct(const EL_INFO *elinfo, void *data)
{
  /*FUNCNAME("graph_level_fct");*/
  static REAL_B bnew[N_VERTICES_2D] = {
    INIT_BARY_2D(1.0,0.0,0.0),
    INIT_BARY_2D(0.0,1.0,0.0),
    INIT_BARY_2D(0.0,0.0,1.0)
  };

  static const REAL *b[3]={bnew[0],bnew[1],bnew[2]};
  const REAL  *x[3];
  REAL_D      coord[N_VERTICES_2D];
  REAL        v[3];
  EL          *el = elinfo->el;
  int         i, j;
  PARAMETRIC  *parametric = elinfo->mesh->parametric;

  el_vec = fill_el_real_vec(NULL, el, drv)->vec;

  el_info       = elinfo;
  if (parametric) 
  {
    el_parametric = parametric;
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D,
			       vertex_lambda_2d, coord);
    for (i = 0; i < 3; i++)
      x[i] = coord[i];
  }
  else 
  {
    el_parametric = NULL;
    for (i = 0; i < 3; i++)
      x[i] = elinfo->coord[i];
  }

  for (i = 0; i < 3; i++)
  {
    v[i] = 0.0;
    for (j=0; j<n_bas_fcts; j++)
      v[i] += el_vec[j] * PHI(bas_fcts, j, b[i]); /* !!! phi[j](b[i]); */
  }

  graph_level_recursive(nrefine, b, v, x);
}

/****************************************************************************/

void graph_level_2d(GRAPH_WINDOW win, const DOF_REAL_VEC *v, REAL level,
		    const GRAPH_RGBCOLOR c, int refine)
{
  FUNCNAME("graph_level_2d");
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  if (!v) return;

  if (v->fe_space && v->fe_space->admin && v->fe_space->admin->mesh)
  {
    if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
      ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
      return;
    }

    OGL_set_std_window(ogl_win);

    glLineWidth(1);
    glColor3fv(c ? c : rgb_black);

    bas_fcts     = v->fe_space->bas_fcts;
    n_bas_fcts   = bas_fcts->n_bas_fcts;
    phi          = bas_fcts->phi;
    drv          = v;
    level_value  = level;
    if (refine >= 0)
      nrefine = refine;
    else
      nrefine = MAX(0, bas_fcts->degree-1);

    mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL | FILL_COORDS,
		  graph_level_fct, NULL);
    
    OGL_FLUSH(ogl_win);
  }
  else
    ERROR("no FE_SPACE OR DOF_ADMIN or MESH\n");
}

/****************************************************************************/

static void graph_level_d_recursive(int refine, const REAL *b[3], REAL v[3],
                                    const REAL *x[3])
{
  int    i,j,k;

  if (refine > 0)       /* refine and call recursively */
  {
    const REAL *bnew[3], *xnew[3];
    REAL_B     bm[3];
    REAL       vnew[3], vm[3], xm[3][DIM_OF_WORLD], phi_b;
    REAL_D     vd;

    for (j = 0; j < 3; j++)
    {
      bm[0][j] = 0.5*(b[1][j] + b[2][j]);
      bm[1][j] = 0.5*(b[0][j] + b[2][j]);
      bm[2][j] = 0.5*(b[0][j] + b[1][j]);
    }

    for (i = 0; i < 3; i++)
    {
	SET_DOW(0.0, vd);
	for (j=0; j<n_bas_fcts; j++) {
	  phi_b = PHI(bas_fcts, j, bm[i]); /* !!! phi[j](bm[i]); */
	    for (k=0; k<DIM_OF_WORLD; ++k)
		vd[k] += el_vec_d[j][k] * phi_b;
	}
	vm[i] = NORM_DOW(vd);
    }

    if (el_parametric)
    {
      el_parametric->coord_to_world(el_info, NULL, N_EDGES_2D,
				    (const REAL_B *)bm, xm);
    }
    else
    {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	xm[0][j] = 0.5*(x[1][j] + x[2][j]);
	xm[1][j] = 0.5*(x[0][j] + x[2][j]);
	xm[2][j] = 0.5*(x[0][j] + x[1][j]);
      }
    }

    bnew[0] = b[0]; bnew[1] = bm[2]; bnew[2] = bm[1]; 
    xnew[0] = x[0]; xnew[1] = xm[2]; xnew[2] = xm[1]; 
    vnew[0] = v[0]; vnew[1] = vm[2]; vnew[2] = vm[1]; 
    graph_level_d_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[2]; bnew[2] = bm[1]; 
    xnew[0] = xm[2]; xnew[1] = x[2]; xnew[2] = xm[1]; 
    vnew[0] = vm[2]; vnew[1] = v[2]; vnew[2] = vm[1]; 
    graph_level_d_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = b[2]; bnew[1] = bm[2]; bnew[2] = bm[0]; 
    xnew[0] = x[2]; xnew[1] = xm[2]; xnew[2] = xm[0]; 
    vnew[0] = v[2]; vnew[1] = vm[2]; vnew[2] = vm[0]; 
    graph_level_d_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[1]; bnew[2] = bm[0]; 
    xnew[0] = xm[2]; xnew[1] = x[1]; xnew[2] = xm[0]; 
    vnew[0] = vm[2]; vnew[1] = v[1]; vnew[2] = vm[0]; 
    graph_level_d_recursive(refine-1, bnew, vnew, xnew);

    return;
  }

  /* draw level line */

  {
    static REAL small = 1.0E-8;
    int   i1, i2, flag[3];
    REAL  s[3];
    float xy[2];

    for (i=0; i<3; i++) {
      i1 = (i+1) % 3;
      if (ABS(v[i1]-v[i]) >= small) {
	s[i] = (level_value - v[i]) / (v[i1]-v[i]);
	flag[i] = (s[i] <= 1.0 && s[i] >= 0.0);
      }
      else {
	flag[i] = 0;
	if (ABS(level_value - v[i]) <= small) {
	  glBegin(GL_LINE_STRIP);
	  xy[0] = x[i][0];
	  xy[1] = x[i][1];
	  glVertex2fv(xy);
	  xy[0] = x[i1][0];
	  xy[1] = x[i1][1];
	  glVertex2fv(xy);
	  glEnd();
	}
      }
    }
    
    for (i=0; i<3; i++) {
      i1 = (i+1) % 3;
      if (flag[i] && flag[i1]) {
	i2 = (i+2) % 3;
	glBegin(GL_LINE_STRIP);
	for (j=0; j<2; j++)
	  xy[j] = x[i][j] + s[i] * (x[i1][j] - x[i][j]);
	glVertex2fv(xy);
	for (j=0; j<2; j++)
	  xy[j] = x[i1][j] + s[i1] * (x[i2][j] - x[i1][j]);
	glVertex2fv(xy);
	  glEnd();
      }
    }
  }
}

static void graph_level_d_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("graph_level_d_fct");
  static REAL_B bnew[3]={INIT_BARY_2D(1.0,0.0,0.0),
			 INIT_BARY_2D(0.0,1.0,0.0),
			 INIT_BARY_2D(0.0,0.0,1.0)};
  static const REAL *b[3]={bnew[0],bnew[1],bnew[2]};
  const REAL  *x[3];
  REAL_D      coord[N_VERTICES_2D], vd;
  REAL        v[3], phi_b;
  EL          *el = elinfo->el;
  int         i, j, k;
  PARAMETRIC  *parametric = elinfo->mesh->parametric;

  if (bas_fcts->get_real_d_vec)
    el_vec_d = fill_el_real_d_vec(NULL, el, drdv)->vec;
  else
    ERROR("no bas_fcts->get_real_d_vec()\n");

  el_info       = elinfo;
  if (parametric)
  {
    el_parametric = parametric;
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D, vertex_lambda_2d, 
			       coord);
    for (i = 0; i < 3; i++)
      x[i] = coord[i];
  }
  else {
    el_parametric = NULL;
    for (i = 0; i < 3; i++)
      x[i] = elinfo->coord[i];
  }

  for (i = 0; i < 3; i++)
  {
    SET_DOW(0.0, vd);
    for (j=0; j<n_bas_fcts; j++) {
      phi_b = PHI(bas_fcts, j, b[i]);      /* !!! phi[j](b[i]);*/
	for (k=0; k<DIM_OF_WORLD; ++k)
	    vd[k] += el_vec_d[j][k] * phi_b;
    }
    v[i] = NORM_DOW(vd);
  }

  graph_level_d_recursive(nrefine, b, v, x);
}

/****************************************************************************/

void graph_level_d_2d(GRAPH_WINDOW win, const DOF_REAL_D_VEC *v, REAL level,
		    const GRAPH_RGBCOLOR c, int refine)
{
  FUNCNAME("graph_level_d_2d");
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  if (!v) return;

  if (level < 0.0) return;  /* no vector norms < 0 */

  if (v->fe_space && v->fe_space->admin && v->fe_space->admin->mesh)
  {
    if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
      ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
      return;
    }
    OGL_set_std_window(ogl_win);
    glLineWidth(1.0);
    glColor3fv(c ? c : rgb_black);

    bas_fcts     = v->fe_space->bas_fcts;
    n_bas_fcts   = bas_fcts->n_bas_fcts;
    phi          = bas_fcts->phi;
    drdv         = v;
    level_value  = level;
    if (refine >= 0)
      nrefine = refine;
    else
      nrefine = MAX(0, bas_fcts->degree-1);

    mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL | FILL_COORDS,
		  graph_level_d_fct, NULL);
    
    OGL_FLUSH(ogl_win);
  }
  else
    ERROR("no FE_SPACE OR DOF_ADMIN or MESH\n");
}

/****************************************************************************/
static void val_minmax_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("val_minmax_fct");
#define NTEST 4
  static REAL_B b[NTEST] = {INIT_BARY_2D(1.0, 0.0, 0.0),
			    INIT_BARY_2D(0.0, 1.0, 0.0),
			    INIT_BARY_2D(0.0, 0.0, 1.0),
			    INIT_BARY_2D(0.333,0.333,0.334)};
  int  i, j, ntest;
  REAL v;

  if (bas_fcts->get_real_vec)
    el_vec = fill_el_real_vec(NULL, elinfo->el, drv)->vec;
  else
    ERROR("no bas_fcts->get_real_vec()\n");

  if (nrefine>0) ntest = NTEST;
  else           ntest = 3;

  for (i = 0; i < ntest; i++)
  {
    v = 0.0;
    for (j=0; j<n_bas_fcts; j++)
      v += el_vec[j] * PHI(bas_fcts, j, b[i]);   /* !!! phi[j](b[i]); */

    val_min = MIN(val_min, v);
    val_max = MAX(val_max, v);
  }
#undef NTEST
}

static void val_d_minmax_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("val_d_minmax_fct");
#define NTEST 4
  static REAL_B b[NTEST] = {INIT_BARY_2D(1.0, 0.0, 0.0),
			    INIT_BARY_2D(0.0, 1.0, 0.0),
			    INIT_BARY_2D(0.0, 0.0, 1.0),
			    INIT_BARY_2D(0.333,0.333,0.334)};
  int    i, j, k, ntest;
  REAL   v, phi_b;
  REAL_D vd;

  if (bas_fcts->get_real_d_vec)
    el_vec_d = fill_el_real_d_vec(NULL, elinfo->el, drdv)->vec;
  else
    ERROR("no bas_fcts->get_real_d_vec()\n");

  if (nrefine>0) ntest = NTEST;
  else           ntest = 3;

  for (i = 0; i < ntest; i++)
  {
    SET_DOW(0.0, vd);
    for (j=0; j<n_bas_fcts; j++) {
      phi_b = PHI(bas_fcts, j, b[i]);      /* !!! phi[j](b[i]); */
	for (k=0; k<DIM_OF_WORLD; ++k)
	    vd[k] += el_vec_d[j][k] * phi_b;
    }
    v = NORM_DOW(vd);

    val_min = MIN(val_min, v);
    val_max = MAX(val_max, v);
  }
#undef NTEST
}

void graph_levels_2d(GRAPH_WINDOW win, const DOF_REAL_VEC *v,
		     int n, REAL const *levels, const GRAPH_RGBCOLOR *color,
		     int refine)
{
  FUNCNAME("graph_levels_2d");
#define MAXLEV 100
  REAL                  lev[MAXLEV];
  GRAPH_RGBCOLOR        col[MAXLEV];
  const REAL            *l;
  const GRAPH_RGBCOLOR  *c;
  int                   nl, i;
  float                 nlr;

  if (!v) return;

  nl = MIN(n, MAXLEV);
  nlr = 1.0 / (float)(MAX(nl,1));

  if (v->fe_space && v->fe_space->admin && v->fe_space->admin->mesh)
  {
    if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
      ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
      return;
    }

    if (levels)
    {
      l = levels;
    }
    else
    {
      drv          = v;
      bas_fcts     = v->fe_space->bas_fcts;
      n_bas_fcts   = bas_fcts->n_bas_fcts;
      phi          = bas_fcts->phi;

      if (refine >= 0)
	nrefine = refine;
      else
	nrefine = MAX(0, bas_fcts->degree-1);

      val_max = -(val_min = 1.0E20);
      mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL,
		    val_minmax_fct, NULL);
      val_max = MAX(val_max, val_min + 1.0E-10);

      for (i = 0; i < nl; i++)
	lev[i] = val_min + ((i-0.5) * nlr) * (val_max-val_min);
      l = lev;
    }

    if (color)
    {
      c = color;
    }
    else
    {
      c = (const GRAPH_RGBCOLOR *) col;
      for (i = 0; i < nl; i++)
      {
	col[i][0] = i * nlr;
	col[i][1] = 4.0 * (i * nlr) * (1.0 - i*nlr);
	col[i][2] = 1.0 - i * nlr;
      }
    }

    for (i = 0; i < nl; i++)
      graph_level_2d(win, v, l[i], c[i], refine);
  }
  else
    ERROR("no FE_SPACE or DOF_ADMIN or MESH\n");
}


void graph_levels_d_2d(GRAPH_WINDOW win, const DOF_REAL_D_VEC *v, int n, 
		       REAL const *levels, const GRAPH_RGBCOLOR *color, 
		       int refine)
{
  FUNCNAME("graph_levels_d_2d");
#define MAXLEV 100
  REAL                  lev[MAXLEV];
  GRAPH_RGBCOLOR        col[MAXLEV];
  const GRAPH_RGBCOLOR  *c;
  const REAL            *l;
  int                   nl, i;
  REAL                  nlr;

  if (!v) return;

  nl = MIN(n, MAXLEV);
  nlr = 1.0 / (float)(MAX(nl,1));

  if (v->fe_space && v->fe_space->admin && v->fe_space->admin->mesh)
  {
    if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
      ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
      return;
    }

    if (levels)
    {
      l = levels;
    }
    else
    {
      drdv         = v;
      bas_fcts     = v->fe_space->bas_fcts;
      n_bas_fcts   = bas_fcts->n_bas_fcts;
      phi          = bas_fcts->phi;

      if (refine >= 0)
	nrefine = refine;
      else
	nrefine = MAX(0, bas_fcts->degree-1);

      val_max = -(val_min = 1.0E20);
      mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL,
		    val_d_minmax_fct, NULL);
      val_max = MAX(val_max, val_min + 1.0E-10);

      for (i = 0; i < nl; i++)
	lev[i] = val_min + ((i-0.5) * nlr) * (val_max-val_min);
      l = lev;
    }

    if (color)
    {
      c = color;
    }
    else
    {
      c = (const GRAPH_RGBCOLOR *) col;
      for (i = 0; i < nl; i++)
      {
	col[i][0] = i * nlr;
	col[i][1] = 4.0 * (i * nlr) * (1.0 - i*nlr);
	col[i][2] = 1.0 - i * nlr;
      }
    }

    for (i = 0; i < nl; i++)
      graph_level_d_2d(win, v, l[i], c[i], refine);
  }
  else
    ERROR("no FE_SPACE or DOF_ADMIN or MESH\n");
}

/****************************************************************************/
static REAL val_fac = 1.0;

static float *val_color(REAL v)
{
  static GRAPH_RGBCOLOR c;
  float  vv = (float) ((v - val_min) * val_fac);
  
  vv = MIN(1.0, vv);
  vv = MAX(0.0, vv);

  c[0] = vv;
  c[1] = 4 * vv * (1.0 - vv);
  c[2] = 1.0 - vv;

  return(c);
}


static void graph_value_recursive(int refine, const REAL *b[3],
				  REAL v[3], const REAL *x[3])
{
  int    i,j;

  if (refine > 0)       /* refine and call recursively */
  {
    const REAL *bnew[3], *xnew[3];
    REAL_B     bm[3];
    REAL       vnew[3], vm[3], xm[3][DIM_OF_WORLD];

    for (j = 0; j < 3; j++)
    {
      bm[0][j] = 0.5*(b[1][j] + b[2][j]);
      bm[1][j] = 0.5*(b[0][j] + b[2][j]);
      bm[2][j] = 0.5*(b[0][j] + b[1][j]);
    }

    for (i = 0; i < 3; i++)
    {
      for (vm[i] = j = 0; j < n_bas_fcts; j++)
	vm[i] += el_vec[j]*PHI(bas_fcts, j, bm[i]);   /* !!! phi[j](bm[i]); */
    }

    if (el_parametric)
    {
      el_parametric->coord_to_world(el_info, NULL, N_EDGES_2D,
				    (const REAL_B *) bm, xm);
    }
    else
    {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	xm[0][j] = 0.5*(x[1][j] + x[2][j]);
	xm[1][j] = 0.5*(x[0][j] + x[2][j]);
	xm[2][j] = 0.5*(x[0][j] + x[1][j]);
      }
    }

    bnew[0] = b[0]; bnew[1] = bm[2]; bnew[2] = bm[1]; 
    xnew[0] = x[0]; xnew[1] = xm[2]; xnew[2] = xm[1]; 
    vnew[0] = v[0]; vnew[1] = vm[2]; vnew[2] = vm[1]; 
    graph_value_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[1]; bnew[2] = bm[0]; 
    xnew[0] = xm[2]; xnew[1] = x[1]; xnew[2] = xm[0]; 
    vnew[0] = vm[2]; vnew[1] = v[1]; vnew[2] = vm[0]; 
    graph_value_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = b[2]; bnew[1] = bm[1]; bnew[2] = bm[0]; 
    xnew[0] = x[2]; xnew[1] = xm[1]; xnew[2] = xm[0]; 
    vnew[0] = v[2]; vnew[1] = vm[1]; vnew[2] = vm[0]; 
    graph_value_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[0]; bnew[1] = bm[1]; bnew[2] = bm[2]; 
    xnew[0] = xm[0]; xnew[1] = xm[1]; xnew[2] = xm[2]; 
    vnew[0] = vm[0]; vnew[1] = vm[1]; vnew[2] = vm[2]; 
    graph_value_recursive(refine-1, bnew, vnew, xnew);

    return;
  }

  /* draw triangle */

  glBegin(GL_TRIANGLES);
  for (i = 0; i < N_VERTICES_2D; i++) {
    glColor3fv(val_color(v[i]));
    glVertex2dv(x[i]);
  }

  glEnd();
}


static void graph_value_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("graph_value_fct");
  static REAL_B  bnew[3]={INIT_BARY_2D(1.0,0.0,0.0),
			  INIT_BARY_2D(0.0,1.0,0.0),
			  INIT_BARY_2D(0.0,0.0,1.0)};
  static const REAL *b[3]={bnew[0],bnew[1],bnew[2]};
  static const REAL *x[3];
  static REAL_D coord[N_VERTICES_2D];
  static REAL   v[3];
  int         i, j;
  PARAMETRIC  *parametric = elinfo->mesh->parametric;

  if (bas_fcts->get_real_vec)
    el_vec = fill_el_real_vec(NULL, elinfo->el, drv)->vec;
  else
    ERROR("no bas_fcts->get_real_vec()\n");

  el_info       = elinfo;
  if (parametric)
  {
    el_parametric = parametric;
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D, vertex_lambda_2d,
			       coord);
    for (i = 0; i < 3; i++)
      x[i] = coord[i];
  }
  else {
    el_parametric = NULL;
    for (i = 0; i < 3; i++)
      x[i] = elinfo->coord[i];
  }

  for (i = 0; i < 3; i++)
  {
    v[i] = 0.0;
    for (j=0; j<n_bas_fcts; j++)
      v[i] += el_vec[j] * PHI(bas_fcts, j, b[i]);        /* !!! phi[j](b[i]); */
  }
      
  graph_value_recursive(nrefine, b, v, x);
}


static void graph_drv_2d(GRAPH_WINDOW win, const DOF_REAL_VEC *v,
			    REAL min, REAL max, int refine)
{
  FUNCNAME("graph_drv_2d");
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
    ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
    return;
  }

  drv          = v;
  bas_fcts     = v->fe_space->bas_fcts;
  n_bas_fcts   = bas_fcts->n_bas_fcts;
  phi          = bas_fcts->phi;

  if (refine >= 0)
    nrefine = refine;
  else
    nrefine = MAX(0, bas_fcts->degree-1);

  if (min < max) {
      val_min = min;
      val_max = max;
  }
  else {
      val_max = -(val_min = 1.0E20);
      mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL, 
		    val_minmax_fct, NULL);
      val_max = MAX(val_max, val_min + 1.0E-10);
  }
  val_fac = 1.0 / (val_max - val_min);


  OGL_set_std_window(ogl_win);

  mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL|FILL_COORDS,
		graph_value_fct, NULL);

  OGL_FLUSH(ogl_win);
}

/****************************************************************************/

static void graph_value_d_recursive(int refine, const REAL *b[3],
				  REAL v[3], const REAL *x[3])
{
  int    i, j, k;
  REAL   phi_b;

  if (refine > 0)       /* refine and call recursively */
  {
    const REAL *bnew[3], *xnew[3];
    REAL_B     bm[3];
    REAL       vnew[3], vm[3], xm[3][DIM_OF_WORLD];
    REAL_D     vd;

    for (j = 0; j < 3; j++)
    {
      bm[0][j] = 0.5*(b[1][j] + b[2][j]);
      bm[1][j] = 0.5*(b[0][j] + b[2][j]);
      bm[2][j] = 0.5*(b[0][j] + b[1][j]);
    }

    for (i = 0; i < 3; i++)
    {
      SET_DOW(0.0, vd);
      for (j = 0; j < n_bas_fcts; j++) 
      {
	phi_b = PHI(bas_fcts, j, bm[i]);  /* !!! phi[j](bm[i]); */
	for (k=0; k<DIM_OF_WORLD; k++)
	  vd[k] += el_vec_d[j][k]*phi_b;
      }
      vm[i] = NORM_DOW(vd);
    }

    if (el_parametric)
    {
      el_parametric->coord_to_world(el_info, NULL, N_EDGES_2D,
				    (const REAL_B *) bm, xm);
    }
    else
    {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	xm[0][j] = 0.5*(x[1][j] + x[2][j]);
	xm[1][j] = 0.5*(x[0][j] + x[2][j]);
	xm[2][j] = 0.5*(x[0][j] + x[1][j]);
      }
    }

    bnew[0] = b[0]; bnew[1] = bm[2]; bnew[2] = bm[1]; 
    xnew[0] = x[0]; xnew[1] = xm[2]; xnew[2] = xm[1]; 
    vnew[0] = v[0]; vnew[1] = vm[2]; vnew[2] = vm[1]; 
    graph_value_d_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[1]; bnew[2] = bm[0]; 
    xnew[0] = xm[2]; xnew[1] = x[1]; xnew[2] = xm[0]; 
    vnew[0] = vm[2]; vnew[1] = v[1]; vnew[2] = vm[0]; 
    graph_value_d_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = b[2]; bnew[1] = bm[1]; bnew[2] = bm[0]; 
    xnew[0] = x[2]; xnew[1] = xm[1]; xnew[2] = xm[0]; 
    vnew[0] = v[2]; vnew[1] = vm[1]; vnew[2] = vm[0]; 
    graph_value_d_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[0]; bnew[1] = bm[1]; bnew[2] = bm[2]; 
    xnew[0] = xm[0]; xnew[1] = xm[1]; xnew[2] = xm[2]; 
    vnew[0] = vm[0]; vnew[1] = vm[1]; vnew[2] = vm[2]; 
    graph_value_d_recursive(refine-1, bnew, vnew, xnew);

    return;
  }

  /* draw triangle */

  glBegin(GL_TRIANGLES);
  for (i = 0; i < N_VERTICES_2D; i++) {
    glColor3fv(val_color(v[i]));
    glVertex2dv(x[i]);
  }

  glEnd();
}


static void graph_value_d_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("graph_value_d_fct");
  static REAL_B bnew[3]={INIT_BARY_2D(1.0,0.0,0.0),
			 INIT_BARY_2D(0.0,1.0,0.0),
			 INIT_BARY_2D(0.0,0.0,1.0)};
  static const REAL *b[3]={bnew[0],bnew[1],bnew[2]};
  static const REAL *x[3];
  static REAL_D coord[N_VERTICES_2D];
  static REAL   v[3], phi_b;
  static REAL_D vd;
  int         i, j, k;
  PARAMETRIC  *parametric = elinfo->mesh->parametric;

  if (bas_fcts->get_real_d_vec)
    el_vec_d = fill_el_real_d_vec(NULL, elinfo->el, drdv)->vec;
  else
    ERROR("no bas_fcts->get_real_d_vec()\n");

  el_info       = elinfo;
  if (parametric)
  {
    el_parametric = parametric;
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D, vertex_lambda_2d, 
			       coord);
    for (i = 0; i < 3; i++)
      x[i] = coord[i];
  }
  else 
  {
    el_parametric = NULL;
    for (i = 0; i < 3; i++)
      x[i] = elinfo->coord[i];
  }

  for (i = 0; i < 3; i++)
  {
    SET_DOW(0.0, vd);
    for (j = 0; j < n_bas_fcts; j++)
    {
      phi_b = PHI(bas_fcts, j, b[i]);      /* !!! phi[j](b[i]); */
      for (k = 0; k < DIM_OF_WORLD; k++)
	vd[k] += el_vec_d[j][k]*phi_b;
    }
    v[i] = NORM_DOW(vd);
  }
      
  graph_value_d_recursive(nrefine, b, v, x);
}


static void graph_drv_d_2d(GRAPH_WINDOW win, const DOF_REAL_D_VEC *v,
			      REAL min, REAL max, int refine)
{
  FUNCNAME("graph_drv_d_2d");
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
    ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
    return;
  }

  drdv         = v;
  bas_fcts     = v->fe_space->bas_fcts;
  n_bas_fcts   = bas_fcts->n_bas_fcts;
  phi          = bas_fcts->phi;

  if (refine >= 0)
    nrefine = refine;
  else
    nrefine = MAX(0, bas_fcts->degree-1);

  if (min < max) {
      val_min = min;
      val_max = max;
  }
  else {
      val_max = -(val_min = 1.0E20);
      mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL, 
		    val_d_minmax_fct, NULL);
      val_max = MAX(val_max, val_min + 1.0E-10);
  }
  val_fac = 1.0 / (val_max - val_min);


  OGL_set_std_window(ogl_win);

  mesh_traverse(v->fe_space->admin->mesh, -1, CALL_LEAF_EL|FILL_COORDS,
		graph_value_d_fct, NULL);

  OGL_FLUSH(ogl_win);
}


/****************************************************************************/
/*  graph_fvalue: general graph_value without DOF_REAL_VEC                  */
/****************************************************************************/
static REAL (*fvalue_fct)(const EL_INFO *, const REAL *lambda) = NULL;

static void gval_minmax_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("gval_minmax_fct");
#define NTEST 4
  static REAL_B b[NTEST] = {INIT_BARY_2D(1.0, 0.0, 0.0),
			    INIT_BARY_2D(0.0, 1.0, 0.0),
			    INIT_BARY_2D(0.0, 0.0, 1.0),
			    INIT_BARY_2D(0.333,0.333,0.334)};
  int  i, ntest;
  REAL v;

  TEST_EXIT(fvalue_fct, "no fvalue_fct\n");

  if (nrefine>0) ntest = NTEST;
  else           ntest = 3;

  for (i = 0; i < ntest; i++)
  {
    v = fvalue_fct(elinfo, b[i]);

    val_min = MIN(val_min, v);
    val_max = MAX(val_max, v);
  }
#undef NTEST
}

/****************************************************************************/

static void graph_fvalue_recursive(int refine, const REAL *b[3],
				   REAL v[3], const REAL *x[3])
{
  int    i,j;

  if (refine > 0)       /* refine and call recursively */
  {
    const REAL *bnew[3], *xnew[3];
    REAL_B     bm[3];
    REAL       vnew[3], vm[3], xm[3][DIM_OF_WORLD];

    for (j = 0; j < 3; j++)
    {
      bm[0][j] = 0.5*(b[1][j] + b[2][j]);
      bm[1][j] = 0.5*(b[0][j] + b[2][j]);
      bm[2][j] = 0.5*(b[0][j] + b[1][j]);
    }

    for (i = 0; i < 3; i++)
    {
      vm[i] = fvalue_fct(el_info, bm[i]);
    }

    if (el_parametric)
    {
      el_parametric->coord_to_world(el_info, NULL, N_EDGES_2D,
				    (const REAL_B *) bm, xm);
    }
    else
    {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	xm[0][j] = 0.5*(x[1][j] + x[2][j]);
	xm[1][j] = 0.5*(x[0][j] + x[2][j]);
	xm[2][j] = 0.5*(x[0][j] + x[1][j]);
      }
    }

    bnew[0] = b[0]; bnew[1] = bm[2]; bnew[2] = bm[1]; 
    xnew[0] = x[0]; xnew[1] = xm[2]; xnew[2] = xm[1]; 
    vnew[0] = v[0]; vnew[1] = vm[2]; vnew[2] = vm[1]; 
    graph_fvalue_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[2]; bnew[2] = bm[1]; 
    xnew[0] = xm[2]; xnew[1] = x[2]; xnew[2] = xm[1]; 
    vnew[0] = vm[2]; vnew[1] = v[2]; vnew[2] = vm[1]; 
    graph_fvalue_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = b[2]; bnew[1] = bm[2]; bnew[2] = bm[0]; 
    xnew[0] = x[2]; xnew[1] = xm[2]; xnew[2] = xm[0]; 
    vnew[0] = v[2]; vnew[1] = vm[2]; vnew[2] = vm[0]; 
    graph_fvalue_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[1]; bnew[2] = bm[0]; 
    xnew[0] = xm[2]; xnew[1] = x[1]; xnew[2] = xm[0]; 
    vnew[0] = vm[2]; vnew[1] = v[1]; vnew[2] = vm[0]; 
    graph_fvalue_recursive(refine-1, bnew, vnew, xnew);

    return;
  }

  /* draw triangle */

  glBegin(GL_TRIANGLES);
  for (i = 0; i < N_VERTICES_2D; i++) {
    glColor3fv(val_color(v[i]));
    glVertex2dv(x[i]);
  }

  glEnd();
}


static void graph_fvalue_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("graph_fvalue_fct");
  static REAL_B bnew[3]={INIT_BARY_2D(1.0,0.0,0.0),
			 INIT_BARY_2D(0.0,1.0,0.0),
			 INIT_BARY_2D(0.0,0.0,1.0)};
  static const  REAL *b[3]={bnew[0],bnew[1],bnew[2]};
  static const  REAL *x[3];
  static REAL_D coord[N_VERTICES_2D];
  static REAL   v[3];
  int           i;
  PARAMETRIC  *parametric = elinfo->mesh->parametric;

  TEST_EXIT(fvalue_fct, "no fvalue_fct\n");

  el_info       = elinfo;
  if (parametric) 
  {
    el_parametric = parametric;
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D, vertex_lambda_2d,
			       coord);
    for (i = 0; i < 3; i++)
      x[i] = coord[i];
  }
  else 
  {
    el_parametric = NULL;
    for (i = 0; i < 3; i++)
      x[i] = elinfo->coord[i];
  }

  for (i = 0; i < 3; i++)
  {
    v[i] = fvalue_fct(elinfo, b[i]);
  }

  graph_fvalue_recursive(nrefine, b, v, x);
}

void graph_fvalues_2d(GRAPH_WINDOW win, MESH *mesh,
		   REAL(*fct)(const EL_INFO *, const REAL *lambda),
		   FLAGS fill_flag, REAL min, REAL max, int refine)
{
  FUNCNAME("graph_fvalues_2d");
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  TEST_EXIT(mesh, "no mesh\n");
  TEST_EXIT(fvalue_fct = fct, "no fct\n");

  if((DIM_OF_WORLD != 2) || (mesh->dim != 2)) {
    ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
    return;
  }

  nrefine = MAX(refine, 0);

  if (min < max) {
      val_min = min;
      val_max = max;
  }
  else {
      val_max = -(val_min = 1.0E20);
      mesh_traverse(mesh, -1, CALL_LEAF_EL | fill_flag, gval_minmax_fct, NULL);
      val_max = MAX(val_max, val_min + 1.0E-10);
  }
  val_fac = 1.0 / (val_max - val_min);

  OGL_set_std_window(ogl_win);

  mesh_traverse(mesh, -1, CALL_LEAF_EL|FILL_COORDS, graph_fvalue_fct, NULL);

  OGL_FLUSH(ogl_win);
}

/****************************************************************************/
/****************************************************************************/
/* MULTIGRID LEVEL DISPLAYS                                                 */
/****************************************************************************/
/****************************************************************************/

void  graph_mesh_mg_2d(GRAPH_WINDOW win, MESH *mesh, const GRAPH_RGBCOLOR c,
		       FLAGS flags, int mg_level)
{
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  if (!mesh) return;

  if((DIM_OF_WORLD != 2) || (mesh->dim != 2)) {
    ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
    return;
  }

  OGL_set_std_window(ogl_win);
  if (c) linecolor_2d = c;
  else   linecolor_2d = rgb_black;
  graph_mesh_flags_2d = flags;
  mesh_traverse(mesh, mg_level, CALL_MG_LEVEL | FILL_COORDS | FILL_BOUND,
		OGLgraph_mesh_fct_2d, NULL);

  OGL_FLUSH(ogl_win);

  return;
}

/****************************************************************************/
static const int  *mg_sort_dof_invers = NULL;
static int        mg_el_vec_size = 0;
static REAL       *mg_el_vec = NULL;
static const REAL *drv_vec = NULL;
static const DOF_ADMIN *mg_admin = NULL;


static void val_minmax_mg_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("val_minmax_mg_fct");
#define NTEST 4
  static REAL_B b[NTEST] = {INIT_BARY_2D(1.0, 0.0, 0.0),
			    INIT_BARY_2D(0.0, 1.0, 0.0),
			    INIT_BARY_2D(0.0, 0.0, 1.0),
			    INIT_BARY_2D(0.333,0.333,0.334)};
  int       i, j, ntest;
  REAL      v;
  const DOF *dof_indices;

  if (bas_fcts->get_dof_indices)
    dof_indices = GET_DOF_INDICES(bas_fcts, elinfo->el, mg_admin, NULL)->vec;
  else {
    ERROR("no bas_fcts->get_dof_indices()\n");
    return;
  }

  if (mg_sort_dof_invers) {
    for (i = 0; i < n_bas_fcts; i++) {
      mg_el_vec[i] = drv_vec[mg_sort_dof_invers[dof_indices[i]]];
    }
  }
  else  /* no mg_sort_dof_invers */
  {
    for (i = 0; i < n_bas_fcts; i++) {
      mg_el_vec[i] = drv_vec[dof_indices[i]];
    }
  }

  if (nrefine>0) ntest = NTEST;
  else           ntest = 3;

  for (i = 0; i < ntest; i++)
  {
    v = 0.0;
    for (j=0; j<n_bas_fcts; j++)
      v += mg_el_vec[j] * PHI(bas_fcts, j, b[i]);     /* !!! phi[j](b[i]); */

    val_min = MIN(val_min, v);
    val_max = MAX(val_max, v);
  }
#undef NTEST
}

static void graph_value_mg_recursive(int refine, const REAL *b[3],
				  REAL v[3], const REAL *x[3])
{
  int    i, j;

  if (refine > 0)       /* refine and call recursively */
  {
    const REAL *bnew[3], *xnew[3];
    REAL_B     bm[3];
    REAL       vnew[3], vm[3], xm[3][DIM_OF_WORLD];

    for (j = 0; j < 3; j++)
    {
      bm[0][j] = 0.5*(b[1][j] + b[2][j]);
      bm[1][j] = 0.5*(b[0][j] + b[2][j]);
      bm[2][j] = 0.5*(b[0][j] + b[1][j]);
    }

    for (i = 0; i < 3; i++)
    {
      vm[i] = 0.0;
      for (j=0; j<n_bas_fcts; j++)
	vm[i] += mg_el_vec[j] * PHI(bas_fcts, j, bm[i]);   /* !!! phi[j](bm[i]); */
    }

    if (el_parametric)
    {
      el_parametric->coord_to_world(el_info, NULL, N_EDGES_2D,
				    (const REAL_B *) bm, xm);
    }
    else
    {
      for (j = 0; j < DIM_OF_WORLD; j++)
      {
	xm[0][j] = 0.5*(x[1][j] + x[2][j]);
	xm[1][j] = 0.5*(x[0][j] + x[2][j]);
	xm[2][j] = 0.5*(x[0][j] + x[1][j]);
      }
    }

    bnew[0] = b[0]; bnew[1] = bm[2]; bnew[2] = bm[1]; 
    xnew[0] = x[0]; xnew[1] = xm[2]; xnew[2] = xm[1]; 
    vnew[0] = v[0]; vnew[1] = vm[2]; vnew[2] = vm[1]; 
    graph_value_mg_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[2]; bnew[2] = bm[1]; 
    xnew[0] = xm[2]; xnew[1] = x[2]; xnew[2] = xm[1]; 
    vnew[0] = vm[2]; vnew[1] = v[2]; vnew[2] = vm[1]; 
    graph_value_mg_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = b[2]; bnew[1] = bm[2]; bnew[2] = bm[0]; 
    xnew[0] = x[2]; xnew[1] = xm[2]; xnew[2] = xm[0]; 
    vnew[0] = v[2]; vnew[1] = vm[2]; vnew[2] = vm[0]; 
    graph_value_mg_recursive(refine-1, bnew, vnew, xnew);

    bnew[0] = bm[2]; bnew[1] = b[1]; bnew[2] = bm[0]; 
    xnew[0] = xm[2]; xnew[1] = x[1]; xnew[2] = xm[0]; 
    vnew[0] = vm[2]; vnew[1] = v[1]; vnew[2] = vm[0]; 
    graph_value_mg_recursive(refine-1, bnew, vnew, xnew);

    return;
  }

  /* draw triangle */

  glBegin(GL_TRIANGLES);
  for (i = 0; i < N_VERTICES_2D; i++) {
    glColor3fv(val_color(v[i]));
    glVertex2dv(x[i]);
  }

  glEnd();
}


static void graph_value_mg_fct(const EL_INFO *elinfo, void *data)
{
  FUNCNAME("graph_value_mg_fct");
  static REAL_B bnew[3]={INIT_BARY_2D(1.0,0.0,0.0),
			 INIT_BARY_2D(0.0,1.0,0.0),
			 INIT_BARY_2D(0.0,0.0,1.0)};
  static const REAL *b[3]={bnew[0],bnew[1],bnew[2]};
  const REAL  *x[3];
  REAL_D      coord[N_VERTICES_2D];
  REAL        v[3];
  int         i, j;
  const DOF   *dof_indices;
  PARAMETRIC  *parametric = elinfo->mesh->parametric;

  if (bas_fcts->get_dof_indices)
    dof_indices = GET_DOF_INDICES(bas_fcts, elinfo->el, mg_admin, NULL)->vec;
  else {
    ERROR("no bas_fcts->get_dof_indices()\n");
    return;
  }

  if (mg_sort_dof_invers) {
    for (i = 0; i < n_bas_fcts; i++) {
      mg_el_vec[i] = drv_vec[mg_sort_dof_invers[dof_indices[i]]];
    }
  }
  else  /* no mg_sort_dof_invers */
  {
    for (i = 0; i < n_bas_fcts; i++) {
      mg_el_vec[i] = drv_vec[dof_indices[i]];
    }
  }

  el_info       = elinfo;
  if (parametric)
  {
    el_parametric = parametric;
    parametric->init_element(elinfo, parametric);
    parametric->coord_to_world(elinfo, NULL, N_VERTICES_2D, vertex_lambda_2d,
			       coord);
    for (i = 0; i < 3; i++)
      x[i] = coord[i];
  }
  else 
  {
    el_parametric = NULL;
    for (i = 0; i < 3; i++)
      x[i] = elinfo->coord[i];
  }

  for (i = 0; i < 3; i++)
  {
    v[i] = 0.0;
    for (j=0; j<n_bas_fcts; j++)
      v[i] += mg_el_vec[j] * PHI(bas_fcts, j, b[i]);     /* !!! phi[j](b[i]); */
  }

  graph_value_mg_recursive(nrefine, b, v, x);
}


void graph_values_mg_2d(GRAPH_WINDOW win, const DOF_REAL_VEC *v,  
		     REAL min, REAL max, int refine,
		     int mg_level, const FE_SPACE *fe_space,
		     const int *sort_dof_invers)
{
  FUNCNAME("graph_values_mg_2d");
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  TEST_EXIT(v && fe_space && fe_space->admin,
	    "no vec or fe_space or admin\n");

  if((DIM_OF_WORLD != 2) || (v->fe_space->admin->mesh->dim != 2)) {
    ERROR("Only implemented for DIM_OF_WORLD==2 and dim==2!\n");
    return;
  }

  drv          = v;
  drv_vec      = drv->vec;
  bas_fcts     = fe_space->bas_fcts;
  mg_admin     = fe_space->admin;
  n_bas_fcts   = bas_fcts->n_bas_fcts;
  phi          = bas_fcts->phi;

  mg_sort_dof_invers = sort_dof_invers;
  if (n_bas_fcts > mg_el_vec_size) {
    mg_el_vec = MEM_REALLOC(mg_el_vec, mg_el_vec_size, n_bas_fcts, REAL);
    mg_el_vec_size = n_bas_fcts;
  }

  if (refine >= 0)
    nrefine = refine;
  else
    nrefine = MAX(0, bas_fcts->degree-1);

  if (min < max) {
      val_min = min;
      val_max = max;
  }
  else {
      val_max = -(val_min = 1.0E20);
      mesh_traverse(fe_space->admin->mesh, mg_level,
		    CALL_MG_LEVEL, val_minmax_mg_fct, NULL);
      MSG("<%s> value range in [%.3le , %.3le]\n", NAME(drv), val_min, val_max);

      val_max = MAX(val_max, val_min + 1.0E-5);
  }
  val_fac = 1.0 / (val_max - val_min);


  OGL_set_std_window(ogl_win);

  mesh_traverse(fe_space->admin->mesh, mg_level, CALL_MG_LEVEL | FILL_COORDS,
		graph_value_mg_fct, NULL);

  graph_mesh_mg_2d(win, fe_space->admin->mesh, rgb_black, 0, mg_level);

  OGL_FLUSH(ogl_win);
}
