/*
** Astrolog (Version 4.00) File: xoptions.c
**
** IMPORTANT NOTICE: the graphics database and chart display routines
** used in this program are Copyright (C) 1991-1993 by Walter D. Pullen
** (cruiser1@stein.u.washington.edu). Permission is granted to freely
** use and distribute these routines provided one doesn't sell,
** restrict, or profit from them in any way. Modification is allowed
** provided these notices remain with any altered or edited versions of
** the program.
**
** The main planetary calculation routines used in this program have
** been Copyrighted and the core of this program is basically a
** conversion to C of the routines created by James Neely as listed in
** Michael Erlewine's 'Manual of Computer Programming for Astrologers',
** available from Matrix Software. The copyright gives us permission to
** use the routines for personal use but not to sell them or profit from
** them in any way.
**
** The PostScript code within the core graphics routines are programmed
** and Copyright (C) 1992-1993 by Brian D. Willoughby
** (brianw@sounds.wa.com). Conditions are identical to those above.
**
** The extended accurate ephemeris databases and formulas are from the
** calculation routines in the program "Placalc" and are programmed and
** Copyright (C) 1989,1991,1993 by Astrodienst AG and Alois Treindl
** (alois@azur.ch). The use of that source code is subject to
** regulations made by Astrodienst Zurich, and the code is not in the
** public domain. This copyright notice must not be changed or removed
** by any user of this program.
**
** Initial programming 8/28,30, 9/10,13,16,20,23, 10/3,6,7, 11/7,10,21/1991.
** X Window graphics initially programmed 10/23-29/1991.
** PostScript graphics initially programmed 11/29-30/1992.
** Last code change made 12/31/1993.
*/

#include "astrolog.h"

#ifdef GRAPH

/*
******************************************************************************
** Chart Graphics Subroutines.
******************************************************************************
*/

/* Return whether the specified object should be displayed in the current  */
/* graphics chart type. For example, don't include the Moon in the solar   */
/* system charts, don't include house cusps in astro-graph, and so on.     */

int Proper(i)
int i;
{
  int j;

  if (modex == MODEL || modex == MODEE)       /* astro-graph or ephem charts */
    j = IsThing(i);
  else if (modex == MODEZ || modex == MODEG)  /* horizon or zenith charts */
    j = IsObject(i);
  else if (modex == MODES)                    /* solar system charts */
    j = (i != _MOO && IsObject(i));
  else
    j = TRUE;
  return j && !ignore[i];                     /* check restriction status */
}


/* Set up arrays with the sine and cosine values of each degree. This is   */
/* used by the wheel chart routines which draw lots of circles. Memory is  */
/* allocated for this array if not already done. The allocation and        */
/* initialization is only done once, the first time the routine is called. */

bool InitCircle()
{
  char string[STRING];
  int i;

  if (circ != NULL)
    return TRUE;
  Allocate(circ, sizeof(circlestruct), circlestruct PTR);
  if (circ == NULL
#ifdef PC
    /* For PC's the array better not cross a segment boundary. */
    || HIWORD(LOWORD(circ) + sizeof(circlestruct)) > 0
#endif
    ) {
    sprintf(string, "Not enough memory for sine table (%d bytes).",
      sizeof(circlestruct));
    PrintError(string);
    return FALSE;
  }
  for (i = 0; i < DEGR; i++) {
    circ->x[i] = COSD((real) i);
    circ->y[i] = SIND((real) i);
  }
  circ->x[DEGR] = circ->x[0]; circ->y[DEGR] = circ->y[0];
  return TRUE;
}


/* Adjust an array of zodiac positions so that no two positions are within   */
/* a certain orb of each other. This is used by the wheel drawing chart      */
/* routines in order to make sure that we don't draw any planet glyphs on    */
/* top of each other. We'll later draw the glyphs at the adjusted positions. */

void FillSymbolRing(symbol)
real *symbol;
{
  real orb = DEFORB*256.0/(real)charty*(real)SCALE, k1, k2, temp;
  int i, j, k = 1, l;

  /* Keep adjusting as long as we can still make changes, or until we do 'n' */
  /* rounds. (With many objects, there just may not be enough room for all.) */

  for (l = 0; k && l < divisions*2; l++) {
    k = 0;
    for (i = 1; i <= total; i++) if (Proper(i)) {

      /* For each object, determine who is closest on either side. */

      k1 = LARGE; k2 = -LARGE;
      for (j = 1; j <= total; j++)
        if (Proper(j) && i != j) {
          temp = symbol[j]-symbol[i];
          if (dabs(temp) > DEGHALF)
            temp -= DEGREES*Sgn(temp);
          if (temp < k1 && temp >= 0.0)
            k1 = temp;
          else if (temp > k2 && temp <= 0.0)
            k2 = temp;
        }

      /* If an object's too close on one side, then we move to the other. */

      if (k2 > -orb && k1 > orb) {
        k = 1; symbol[i] = Mod(symbol[i]+orb*0.51+k2*0.49);
      } else if (k1 < orb && k2 < -orb) {
        k = 1; symbol[i] = Mod(symbol[i]-orb*0.51+k1*0.49);

      /* If we are bracketed by close objects on both sides, then let's move */
      /* to the midpoint, so we are as far away as possible from either one. */

      } else if (k2 > -orb && k1 < orb) {
        k = 1; symbol[i] = Mod(symbol[i]+(k1+k2)*0.5);
      }
    }
  }
}


/* Adjust an array of longitude positions so that no two are within a    */
/* certain orb of each other. This is used by the astro-graph routine to */
/* make sure we don't draw any planet glyphs marking the lines on top of */
/* each other. This is almost identical to the FillSymbolRing() routine  */
/* used by the wheel charts; however, there the glyphs are placed in a   */
/* continuous ring, while here we have the left and right screen edges.  */
/* Also, here we are placing two sets of planets at the same time.       */

void FillSymbolLine(symbol)
real *symbol;
{
  real orb = DEFORB*1.35*(real)SCALE, max = DEGREES, k1, k2, temp;
  int i, j, k = 1, l;

  if (modex != MODEE)
    max *= (real)SCALE;
  else
    orb *= DEGREES/(real)chartx;

  /* Keep adjusting as long as we can still make changes. */

  for (l = 0; k && l < divisions*2; l++) {
    k = 0;
    for (i = 1; i <= total*2; i++)
      if (Proper((i+1)/2) && symbol[i] >= 0.0) {

        /* For each object, determine who is closest to the left and right. */

        k1 = max-symbol[i]; k2 = -symbol[i];
        for (j = 1; j <= total*2; j++) {
          if (Proper((j+1)/2) && i != j) {
            temp = symbol[j]-symbol[i];
            if (temp < k1 && temp >= 0.0)
              k1 = temp;
            else if (temp > k2 && temp <= 0.0)
              k2 = temp;
          }
        }

        /* If an object's too close on one side, then we move to the other. */

        if (k2 > -orb && k1 > orb) {
          k = 1; symbol[i] = symbol[i]+orb*0.51+k2*0.49;
        } else if (k1 < orb && k2 < -orb) {
          k = 1; symbol[i] = symbol[i]-orb*0.51+k1*0.49;
        } else if (k2 > -orb && k1 < orb) {
          k = 1; symbol[i] = symbol[i]+(k1+k2)*0.5;
        }
      }
  }
}


/* Another stream reader, this one is used by the globe drawing routine: */
/* for the next body of land/water, return its name (and color), its     */
/* longitude and latitude, and a vector description of its outline.      */

int ReadWorldData(nam, loc, lin)
char **nam, **loc, **lin;
{
  static char FAR **datapointer = worlddata;

  *loc = *datapointer++;
  *lin = *datapointer++;
  *nam = *datapointer++;
  if (*loc[0]) {
    if ((exdisplay & DASHXP0) && xfile)
      fprintf(stdout, "%s\n", *nam+1);
    return TRUE;
  }
  datapointer = worlddata;    /* Reset stream when no data left. */
  return FALSE;
}


/* Given longitude and latitude values on a globe, return the window        */
/* coordinates corresponding to them. In other words, project the globe     */
/* onto the view plane, and return where our coordinates got projected to,  */
/* as well as whether our location is hidden on the back side of the globe. */

int GlobeCalc(x1, y1, u, v, cx, cy, rx, ry, deg)
real x1, y1;
int *u, *v, cx, cy, rx, ry, deg;
{
  real j, siny1;

  /* Compute coordinates for a general globe invoked with -XG switch. */

  if (modex == MODEG) {
    x1 = Mod(x1+(real)deg);    /* Shift by current globe rotation value. */
    if (tilt != 0.0) {
      x1 = DTOR(x1); y1 = DTOR(DEGQUAD-y1);       /* Do another coordinate  */
      CoorXform(&x1, &y1, tilt / DEGRAD);         /* shift if the globe's   */
      x1 = Mod(RTOD(x1)); y1 = DEGQUAD-RTOD(y1);  /* equator is tilted any. */
    }
    *v = cy + (int) ((real)ry*-COSD(y1)-ROUND);
    *u = cx + (int) ((real)rx*-COSD(x1)*SIND(y1)-ROUND);
    return x1 > DEGHALF;
  }

  /* Compute coordinates for a polar globe invoked with -XP switch. */

  siny1 = SIND(y1);
  j = xbonus ? DEGQUAD+x1+deg : 270.0-x1-deg;
  *v = cy + (int) (siny1*(real)ry*SIND(j)-ROUND);
  *u = cx + (int) (siny1*(real)rx*COSD(j)-ROUND);
  return xbonus ? y1 < DEGQUAD : y1 > DEGQUAD;
}


/* Draw a globe in the window, based on the specified rotational and tilt  */
/* values. In addition, we may draw in each planet at its zenith position. */

void DrawGlobe(deg)
int deg;
{
  char *nam, *loc, *lin, d;
  int X[TOTAL+1], Y[TOTAL+1], M[TOTAL+1], N[TOTAL+1],
    cx = chartx/2, cy = charty/2, rx, ry, lon, lat, unit = 12*SCALE,
    x, y, m, n, u, v, i, J, k, l, o;
  real planet1[TOTAL+1], planet2[TOTAL+1], x1, y1, j;
  colpal c;

  rx = cx-1; ry = cy-1;

  /* Loop through each coastline string, drawing visible parts on the globe. */

  while (ReadWorldData(&nam, &loc, &lin)) {
    i = nam[0]-'0';
    c = (modex == MODEG && xbonus) ? gray :
      (i ? rainbowcolor[i] : maincolor[6]);
    DrawColor(c);

    /* Get starting longitude and latitude of current coastline piece. */

    lon = (loc[0] == '+' ? 1 : -1)*
      ((loc[1]-'0')*100 + (loc[2]-'0')*10 + (loc[3]-'0'));
    lat = (loc[4] == '+' ? 1 : -1)*((loc[5]-'0')*10 + (loc[6]-'0'));
    x = 180-lon;
    y = 90-lat;
    GlobeCalc((real) x, (real) y, &m, &n, cx, cy, rx, ry, deg);

    /* Go down the coastline piece, drawing each visible segment on globe. */

    o = (tilt == 0.0 && modex != MODEP);
    k = l = TRUE;
    while (d = *lin++) {
      if (d == 'L' || d == 'H' || d == 'G')
        x--;
      else if (d == 'R' || d == 'E' || d == 'F')
        x++;
      if (d == 'U' || d == 'H' || d == 'E')
        y--;
      else if (d == 'D' || d == 'G' || d == 'F')
        y++;
      if (x > 359)
        x = 0;
      else if (x < 0)
        x = 359;
      if (o) {
        k = x+deg;
        if (k > 359)
          k -= DEGR;
        k = (k <= 180);
      }
      if (k && !GlobeCalc((real) x, (real) y, &u, &v, cx, cy, rx, ry, deg)) {
        if (l)
          DrawLine(m, n, u, v);
        m = u; n = v;
        l = TRUE;
      } else
        l = FALSE;
    }
  }

  DrawColor(on);
  DrawEllipse(0, 0, chartx-1, charty-1);

  /* Now, only if we are in bonus chart mode, draw each planet at its */
  /* zenith location on the globe, assuming that location is visible. */

  if (modex != MODEG || !xbonus)
    return;
  j = Lon;
  if (j < 0.0)
    j += DEGREES;
  for (i = 1; i <= total; i++) {
    planet1[i] = DTOR(planet[i]);
    planet2[i] = DTOR(planetalt[i]);
    EclToEqu(&planet1[i], &planet2[i]);    /* Calculate zenith long. & lat. */
  }
  for (i = 1; i <= total; i++) if (Proper(i)) {
    x1 = planet1[_MC]-planet1[i];
    if (x1 < 0.0)
      x1 += 2.0*PI;
    if (x1 > PI)
      x1 -= 2.0*PI;
    x1 = Mod(DEGHALF-j-RTOD(x1));
    y1 = DEGQUAD-RTOD(planet2[i]);
    X[i] = GlobeCalc(x1, y1, &u, &v, cx, cy, rx, ry, deg) ? -1000 : u;
    Y[i] = v; M[i] = X[i]; N[i] = Y[i]+unit/2;
  }

  /* Now that we have the coordinates of each object, figure out where to    */
  /* draw the glyphs. Again, we try not to draw glyphs on top of each other. */

  for (i = 1; i <= total; i++) if (Proper(i)) {
    k = l = chartx+charty;

    /* For each planet, we draw the glyph either right over or right under */
    /* the actual zenith location point. So, find out the closest distance */
    /* of any other planet assuming we place ours at both possibilities.   */

    for (J = 1; J < i; J++) if (Proper(J)) {
      k = MIN(k, abs(M[i]-M[J])+abs(N[i]-N[J]));
      l = MIN(l, abs(M[i]-M[J])+abs(N[i]-unit-N[J]));
    }

    /* Normally, we put the glyph right below the actual point. If however   */
    /* another planet is close enough to have their glyphs overlap, and the  */
    /* above location is better of, then we'll draw the glyph above instead. */

    if (k < unit || l < unit)
      if (k < l)
        N[i] -= unit;
  }
  for (i = total; i >= 1; i--) if (X[i] >= 0 && Proper(i))      /* Draw the */
    DrawObject(i, M[i], N[i]);                                  /* glyphs.  */
  for (i = total; i >= 1; i--) if (X[i] >= 0 && Proper(i)) {
    DrawColor(objectcolor[i]);
    DrawSpot(X[i], Y[i]);
  }
}


/* Draw one "Ley line" on the world map, based coordinates given in terms of */
/* longitude and vertical fractional distance from the center of the earth.  */

void DrawLeyLine(l1, f1, l2, f2)
real l1, f1, l2, f2;
{
  l1 = Mod(l1); l2 = Mod(l2);

  /* Convert vertical fractional distance to a corresponding coordinate. */

  f1 = DEGQUAD-ASIN(f1)/(PI/2.0)*DEGQUAD;
  f2 = DEGQUAD-ASIN(f2)/(PI/2.0)*DEGQUAD;
  DrawWrap((int) (l1*(real)SCALE+ROUND)+1,
           (int) (f1*(real)SCALE+ROUND)+1,
           (int) (l2*(real)SCALE+ROUND)+1,
           (int) (f2*(real)SCALE+ROUND)+1, 1, chartx-2);
}


/* Draw the main set of planetary Ley lines on the map of the world. This */
/* consists of drawing an icosahedron and then a dodecahedron lattice.    */

void DrawLeyLines(deg)
int deg;
{
  real off = (real)deg, phi, h, h1, h2, r, i;

  phi = (sqrt(5.0)+1.0)/2.0;                     /* Icosahedron constants. */
  h = 1.0/(phi*2.0-1.0);
  DrawColor(aspectcolor[10]);
  for (i = off; i < DEGREES+off; i += 72.0) {    /* Draw icosahedron edges. */
    DrawLeyLine(i, h, i+72.0, h);
    DrawLeyLine(i-36.0, -h, i+36.0, -h);
    DrawLeyLine(i, h, i, 1.0);
    DrawLeyLine(i+36.0, -h, i+36.0, -1.0);
    DrawLeyLine(i, h, i+36.0, -h);
    DrawLeyLine(i, h, i-36.0, -h);
  }
  r = 1.0/sqrt(3.0)/phi/cos(DTOR(54.0));         /* Dodecahedron constants. */
  h2 = sqrt(1.0-r*r); h1 = h2/(phi*2.0+1.0);
  DrawColor(aspectcolor[13]);
  for (i = off; i < DEGREES+off; i += 72.0) {    /* Draw docecahedron edges. */
    DrawLeyLine(i-36.0, h2, i+36.0, h2);
    DrawLeyLine(i, -h2, i+72.0, -h2);
    DrawLeyLine(i+36.0, h2, i+36.0, h1);
    DrawLeyLine(i, -h2, i, -h1);
    DrawLeyLine(i+36.0, h1, i+72.0, -h1);
    DrawLeyLine(i+36.0, h1, i, -h1);
  }
}


/* Draw a map of the world on the screen. This is similar to drawing the  */
/* globe, but is simplified because this is just a rectangular image, and */
/* the window coordinates are proportional to the longitude and latitude. */

void DrawWorld(deg)
int deg;
{
  char *nam, *loc, *lin, d;
  int lon, lat, x, y, xold, yold, i;
  colpal c;

  /* Loop through each coastline string, drawing it on the world map. */

  while (ReadWorldData(&nam, &loc, &lin)) {
    i = nam[0]-'0';
    c = modex == MODEL ? on : (i ? rainbowcolor[i] : maincolor[6]);

    /* Get starting longitude and latitude of current coastline piece. */

    lon = (loc[0] == '+' ? 1 : -1)*
      ((loc[1]-'0')*100 + (loc[2]-'0')*10 + (loc[3]-'0'));
    lat = (loc[4] == '+' ? 1 : -1)*((loc[5]-'0')*10 + (loc[6]-'0'));
    xold = x = (int) Mod((real)(181-lon+deg));
    yold = y = 91-lat;

    /* Go down the coastline piece, drawing each segment on world map. */

    for (i = 0; d = lin[i]; i++) {
      if (d == 'L' || d == 'H' || d == 'G')
        x--;
      else if (d == 'R' || d == 'E' || d == 'F')
        x++;
      if (d == 'U' || d == 'H' || d == 'E')
        y--;
      else if (d == 'D' || d == 'G' || d == 'F')
        y++;
      if (x > DEGR) {
        x = 1;
        xold = 0;
      }

      /* If we are doing a Mollewide map projection, then transform the */
      /* coordinates appropriately before drawing the segment.          */

      DrawColor(c);
      if ((exdisplay & DASHXW0) > 0 && modex != MODEL)
        DrawLine((180+(xold-180)*
          (int)sqrt((real)(32400-4*(yold-91)*(yold-91)))/180)*SCALE,
          yold*SCALE,
          (180+(x-180)*(int)sqrt((real)(32400-4*(y-91)*(y-91)))/180)*SCALE,
          y*SCALE);
      else
        DrawLine(xold*SCALE, yold*SCALE, x*SCALE, y*SCALE);
      if (x < 1)
        x = DEGR;
      xold = x; yold = y;
    }
  }

  /* Again, if we are doing the non-rectangular Mollewide map projection, */
  /* draw the outline of the globe/map itself.                            */

  if ((exdisplay & DASHXW0) > 0 && modex != MODEL) {
    if (!xbonus) {
      DrawColor(on);
      for (xold = 0, y = -89; y <= 90; y++, xold = x)
        for (x = (int)(sqrt((real)(32400-4*y*y))+ROUND), i = -1; i < 2; i += 2)
          DrawLine((180+i*xold)*SCALE, (90+y)*SCALE,
            (180+i*x)*SCALE, (91+y)*SCALE);
    }
  }
}


/* Given a zodiac degree, adjust it if need be to account for the expanding */
/* and compacting of parts the zodiac that happen when we display a graphic */
/* wheel chart such that all the houses appear the same size.               */

real XHousePlaceIn(deg)
real deg;
{
  int in;

  if (modex == MODEv)    /* We only adjust for the -w -X combination. */
    return deg;
  in = HousePlaceIn(deg);
  return Mod(STOZ(in)+MinDistance(house[in], deg)/
    MinDistance(house[in], house[Mod12(in+1)])*30.0);
}


/*
******************************************************************************
** Multiple Chart Graphics Subprograms.
******************************************************************************
*/

/* Draw another wheel chart; however, this time we have two rings of planets */
/* because we are doing a relationship chart between two sets of data. This  */
/* chart is obtained when the -r0 is combined with the -X switch.            */

void XChartWheelRelation()
{
  real xsign[SIGNS+1], xhouse1[SIGNS+1], xplanet1[TOTAL+1], xplanet2[TOTAL+1],
    symbol[TOTAL+1];
  int cx, cy, i, j;
  real asc, unitx, unity, px, py, temp;

  /* Set up variables and temporarily automatically decrease the horizontal */
  /* chart size to leave room for the sidebar if that mode is in effect.    */

  if (xtext && !(exdisplay & DASHv0))
    chartx -= SIDET;
  cx = chartx/2 - 1; cy = charty/2 - 1;
  unitx = (real)cx; unity = (real)cy;
  asc = xeast ? planet1[abs(xeast)]+90*(xeast < 0) : house1[1];
  InitCircle();

  /* Fill out arrays with the degree of each object, cusp, and sign glyph. */

  if (modex == MODEv) {
    for (i = 1; i <= SIGNS; i++)
      xhouse1[i] = PZ(house1[i]);
  } else {
    asc -= house1[1];
    for (i = 1; i <= SIGNS; i++)
      xhouse1[i] = PZ(STOZ(i));
  }
  for (i = 1; i <= SIGNS; i++)
    xsign[i] = PZ(XHousePlaceIn(STOZ(i)));
  for (i = 1; i <= total; i++)
    xplanet1[i] = PZ(XHousePlaceIn(planet1[i]));
  for (i = 1; i <= total; i++)
    xplanet2[i] = PZ(XHousePlaceIn(planet2[i]));

  /* Draw the horizon and meridian lines across whole chart, and draw the */
  /* zodiac and house rings, exactly like before. We are drawing only the */
  /* houses of one of the two charts in the relationship, however.        */

  DrawColor(hilite);
  DrawDash(cx+POINT(unitx, 0.99, PX(xhouse1[1])),
           cy+POINT(unity, 0.99, PY(xhouse1[1])),
           cx+POINT(unitx, 0.99, PX(xhouse1[7])),
           cy+POINT(unity, 0.99, PY(xhouse1[7])), !xcolor);
  DrawDash(cx+POINT(unitx, 0.99, PX(xhouse1[10])),
           cy+POINT(unity, 0.99, PY(xhouse1[10])),
           cx+POINT(unitx, 0.99, PX(xhouse1[4])),
           cy+POINT(unity, 0.99, PY(xhouse1[4])), !xcolor);
  for (i = 0; i < DEGR; i += 5-(xcolor || psfile || metafile)*4) {
    temp = PZ(XHousePlaceIn((real)i));
    px = PX(temp); py = PY(temp);
    DrawColor(i%5 ? gray : on);
    DrawDash(cx+POINT(unitx, 0.78, px), cy+POINT(unity, 0.78, py),
      cx+POINT(unitx, 0.82, px), cy+POINT(unity, 0.82, py),
      ((psfile || metafile) && i%5)*2);
  }

  DrawColor(on);
  DrawCircle(cx, cy, (int)(unitx*0.95+ROUND), (int)(unity*0.95+ROUND));
  DrawCircle(cx, cy, (int)(unitx*0.82+ROUND), (int)(unity*0.82+ROUND));
  DrawCircle(cx, cy, (int)(unitx*0.78+ROUND), (int)(unity*0.78+ROUND));
  DrawCircle(cx, cy, (int)(unitx*0.70+ROUND), (int)(unity*0.70+ROUND));

  for (i = 1; i <= SIGNS; i++) {
    temp = xsign[i];
    DrawColor(on);
    DrawLine(cx+POINT(unitx, 0.95, PX(temp)),
      cy+POINT(unity, 0.95, PY(temp)),
      cx+POINT(unitx, 0.82, PX(temp)),
      cy+POINT(unity, 0.82, PY(temp)));
    DrawLine(cx+POINT(unitx, 0.78, PX(xhouse1[i])),
      cy+POINT(unity, 0.78, PY(xhouse1[i])),
      cx+POINT(unitx, 0.70, PX(xhouse1[i])),
      cy+POINT(unity, 0.70, PY(xhouse1[i])));
    if (xcolor && i%3 != 1) {
      DrawColor(gray);
      DrawDash(cx, cy, cx+POINT(unitx, 0.70, PX(xhouse1[i])),
        cy+POINT(unity, 0.70, PY(xhouse1[i])), 1);
    }
    temp = Midpoint(temp, xsign[Mod12(i+1)]);
    DrawColor(signcolor(i));
    DrawSign(i, cx+POINT(unitx, 0.885, PX(temp)),
      cy+POINT(unity, 0.885, PY(temp)));
    temp = Midpoint(xhouse1[i], xhouse1[Mod12(i+1)]);
    DrawHouse(i, cx+POINT(unitx, 0.74, PX(temp)),
      cy+POINT(unity, 0.74, PY(temp)));
  }

  /* Draw the outer ring of planets (based on the planets in the chart     */
  /* which the houses do not reflect - the houses belong to the inner ring */
  /* below). Draw each glyph, a line from it to its actual position point  */
  /* in the outer ring, and then draw another line from this point to a    */
  /* another dot at the same position in the inner ring as well.           */

  for (i = 1; i <= total; i++)
    symbol[i] = xplanet2[i];
  FillSymbolRing(symbol);
  for (i = 1; i <= total; i++) if (Proper(i)) {
    if (xlabel) {
      temp = symbol[i];
      DrawColor(ret2[i] < 0.0 ? gray : on);
      DrawDash(cx+POINT(unitx, 0.58, PX(xplanet2[i])),
        cy+POINT(unity, 0.58, PY(xplanet2[i])),
        cx+POINT(unitx, 0.61, PX(temp)),
        cy+POINT(unity, 0.61, PY(temp)),
        (ret2[i] < 0.0 ? 1 : 0) - xcolor);
      DrawObject(i, cx+POINT(unitx, 0.65, PX(temp)),
        cy+POINT(unity, 0.65, PY(temp)));
    }
    DrawColor(objectcolor[i]);
    DrawPoint(cx+POINT(unitx, 0.56, PX(xplanet2[i])),
      cy+POINT(unity, 0.56, PY(xplanet2[i])));
    DrawPoint(cx+POINT(unitx, 0.43, PX(xplanet2[i])),
      cy+POINT(unity, 0.43, PY(xplanet2[i])));
    DrawColor(ret2[i] < 0.0 ? gray : on);
    DrawDash(cx+POINT(unitx, 0.45, PX(xplanet2[i])),
      cy+POINT(unity, 0.45, PY(xplanet2[i])),
      cx+POINT(unitx, 0.54, PX(xplanet2[i])),
      cy+POINT(unity, 0.54, PY(xplanet2[i])), 2-xcolor);
  }

  /* Now draw the inner ring of planets. If it weren't for the outer ring,  */
  /* this would be just like the standard non-relationship wheel chart with */
  /* only one set of planets. Again, draw glyph, and a line to true point.  */

  for (i = 1; i <= total; i++) {
    symbol[i] = xplanet1[i];
  }
  FillSymbolRing(symbol);
  for (i = 1; i <= total; i++) if (Proper(i)) {
    if (xlabel) {
      temp = symbol[i];
      DrawColor(ret1[i] < 0.0 ? gray : on);
      DrawDash(cx+POINT(unitx, 0.45, PX(xplanet1[i])),
        cy+POINT(unity, 0.45, PY(xplanet1[i])),
        cx+POINT(unitx, 0.48, PX(temp)),
        cy+POINT(unity, 0.48, PY(temp)),
        (ret1[i] < 0.0 ? 1 : 0) - xcolor);
      DrawObject(i, cx+POINT(unitx, 0.52, PX(temp)),
        cy+POINT(unity, 0.52, PY(temp)));
    } else
      DrawColor(objectcolor[i]);
    DrawPoint(cx+POINT(unitx, 0.43, PX(xplanet1[i])),
      cy+POINT(unity, 0.43, PY(xplanet1[i])));
  }

  /* Draw lines connecting planets between the two charts that have aspects. */

  if (!xbonus) {                  /* Don't draw aspects in bonus mode. */
    CreateGridRelation(FALSE);
    for (j = total; j >= 1; j--)
      for (i = total; i >= 1; i--)
        if (grid->n[i][j] && Proper(i) && Proper(j)) {
          DrawColor(aspectcolor[grid->n[i][j]]);
          DrawDash(cx+POINT(unitx, 0.41, PX(xplanet1[j])),
            cy+POINT(unity, 0.41, PY(xplanet1[j])),
            cx+POINT(unitx, 0.41, PX(xplanet2[i])),
            cy+POINT(unity, 0.41, PY(xplanet2[i])),
            abs(grid->v[i][j]/60/2));
        }
  }

  /* Go draw sidebar with chart information and positions if need be. */

  DrawInfo();
}


/* Draw an aspect (or midpoint) grid in the window, between the planets in  */
/* two different charts, with the planets labeled at the top and side. This */
/* chart is done when the -g switch is combined with the -r0 and -X switch. */
/* Like above, the chart always has a (definable) fixed number of cells.    */

void XChartGridRelation()
{
  char string[STRING];
  int unit, siz, x, y, i, j, k, l;
  colpal c;

  unit = CELLSIZE*SCALE; siz = (gridobjects+1)*unit;
  CreateGridRelation(xbonus != (exdisplay & DASHg0) > 0);
  for (y = 0, j = -1; y <= gridobjects; y++) {
    do {
      j++;
    } while (ignore[j] && j <= total);
    DrawColor(gray);
    DrawDash(0, (y+1)*unit, siz, (y+1)*unit, !xcolor);
    DrawDash((y+1)*unit, 0, (y+1)*unit, siz, !xcolor);
    DrawColor(hilite);
    DrawEdge(0, y*unit, unit, (y+1)*unit);
    DrawEdge(y*unit, 0, (y+1)*unit, unit);
    if (j <= total) for (x = 0, i = -1; x <= gridobjects; x++) {
      do {
        i++;
      } while (ignore[i] && i <= total);

      /* Again, we are looping through each cell in each row and column. */

      if (i <= total) {
        turtlex = x*unit+unit/2;
        turtley = y*unit+unit/2 - (SCALE/scalet > 2 ? 5*scalet : 0);

        /* If current cell is on top row or left hand column, draw glyph */
        /* of planet owning the particular row or column in question.    */

        if (y == 0 || x == 0) {
          if (x+y > 0)
            DrawObject(j == 0 ? i : j, turtlex, turtley);
        } else {

        /* Otherwise, draw glyph of aspect in effect, or glyph of */
        /* sign of midpoint, between the two planets in question. */

          if (xbonus == (exdisplay & DASHg0) > 0) {
            DrawColor(c = aspectcolor[grid->n[i][j]]);
            DrawAspect(grid->n[i][j], turtlex, turtley);
          } else {
            DrawColor(c = signcolor(grid->n[i][j]));
            DrawSign(grid->n[i][j], turtlex, turtley);
          }
        }

        /* Again, when scale size is 300, print some text in current cell: */

        if (SCALE/scalet > 2 && xlabel) {

          /* For top and left edges, print sign and degree of the planet. */

          if (y == 0 || x == 0) {
            if (x+y > 0) {
              k = ZTOS(y == 0 ? planet2[i] : planet1[j]);
              l = (int)((y == 0 ? planet2[i] : planet1[j])-STOZ(k));
              c = signcolor(k);
              sprintf(string, "%c%c%c %02d", SIGNAM(k), l);

              /* For extreme upper left corner, print some little arrows */
              /* pointing out chart1's planets and chart2's planets.     */

            } else {
              c = hilite;
              sprintf(string, "1v 2->");
            }
          } else {
            k = abs(grid->v[i][j]);

            /* For aspect cells, print the orb in degrees and minutes. */

            if (xbonus == (exdisplay & DASHg0) > 0)
              if (grid->n[i][j])
                sprintf(string, "%c%d %02d'", k != grid->v[i][j] ? (exdisplay &
                  DASHga ? 'a' : '-') : (exdisplay & DASHga ? 's' : '+'),
                  k/60, k%60);
              else
                sprintf(string, "");

            /* For midpoint cells, print degree and minute. */

            else
              sprintf(string, "%2d %02d'", k/60, k%60);
          }
          DrawColor(c);
          DrawText(string, x*unit+unit/2, (y+1)*unit-3*scalet, TRUE);
        }
      }
    }
  }
}


#ifdef BIORHYTHM
#define BIODAYS 14     /* Number of days to include in each half of chart. */

/* Draw a graphic biorhythm chart on the screen, as is done when the -rb    */
/* switch is combined with -X. This is technically a relationship chart in  */
/* that biorhythm status is determined by a natal chart time at another     */
/* later time. For the day in question, and for two weeks before and after, */
/* the Physical, Emotional, and Mental percentages are plotted.             */

void XChartBiorhythm()
{
  char string[6], *c;
  real jd, r, a;
  int x1, x2, xs, cx, y1, y2, ys, cy, i, j, k, x, y, x0, y0;

  k = FONTX*6*scalet;
  x1 = k; x2 = chartx-k; xs = x2-x1; cx = (x1+x2)/2;
  k = CELLSIZE;
  y1 = k; y2 = charty-k; ys = y2-y1; cy = (y1+y2)/2;

  /* Create a dotted day/percentage grid to graph on. */
  DrawColor(gray);
  DrawDash(x1, cy, x2, cy, 1);
  DrawDash(cx, y1, cx, y2, 1);
  for (j = -BIODAYS+1; j <= BIODAYS-1; j++) {
    x = x1 + MULTDIV(xs, j+BIODAYS, BIODAYS*2);
    for (k = -90; k <= 90; k += 10) {
      y = y1 + MULTDIV(ys, 100+k, 200);
      DrawPoint(x, y);
    }
  }

  /* Now actually draw the three biorhythm curves. */
  for (i = 1; i <= 3; i++) {
    jd = floor(JD + ROUND);
    switch (i) {
    case 1: r = _PHY; c = "PHYS"; DrawColor(elemcolor[_FIR]); break;
    case 2: r = _EMO; c = "EMOT"; DrawColor(elemcolor[_WAT]); break;
    case 3: r = _INT; c = "INTE"; DrawColor(elemcolor[_EAR]); break;
    }
    for (jd -= (real)BIODAYS, j = -BIODAYS; j <= BIODAYS; j++, jd += 1.0) {
      a = Biorhythm(jd, r);
      x = x1 + MULTDIV(xs, j+BIODAYS, BIODAYS*2);
      y = y1 + (int)((real)ys * (100.0-a) / 200.0);
      if (j > -BIODAYS)
        DrawLine(x0, y0, x, y);
      else
        DrawText(c, x1/2, y+2*scalet, FALSE);
      x0 = x; y0 = y;
    }
  }

  DrawColor(hilite);
  /* Label biorhythm percentages along right vertical axis. */
  for (k = -100; k <= 100; k += 10) {
    sprintf(string, "%c%3d%%", k < 0 ? '-' : '+', abs(k));
    y = y1 + MULTDIV(ys, 100-k, 200);
    DrawText(string, (x2+chartx)/2, y+2*scalet, FALSE);
  }
  /* Label days on top horizontal axis. */
  for (j = -BIODAYS; j <= BIODAYS; j += 2) {
    x = x1 + MULTDIV(xs, j+BIODAYS, BIODAYS*2);
    sprintf(string, "%c%d", j < 0 ? '-' : '+', abs(j));
    DrawText(string, x, y1-2*scalet, TRUE);
  }
  DrawEdge(x1, y1, x2, y2);
}
#endif


/* Create a chart in the window based on the current graphics chart mode. */
/* This is the main dispatch routine for all of the program's graphics.   */

void XChart()
{
  char string[STRING];
  int i, j, k;

  scalet = psfile ? PSMUL : (metafile ? METAMUL : 1);

  DrawClearScreen();
  switch (modex) {
  case MODEv:
  case MODEw:
    if (relation > DASHr0)
      XChartWheel();
    else
      XChartWheelRelation();
    break;
  case MODEL:
    DrawWorld(degree);     /* First draw map of world.           */
    XChartAstroGraph();    /* Then draw astro-graph lines on it. */
    break;
  case MODEg:
    if (relation > DASHr0)
      XChartGrid();
    else
      XChartGridRelation();
    break;
  case MODEZ:
    if (exdisplay & DASHZ0)
      XChartHorizonSky();
    else
      XChartHorizon();
    break;
  case MODES:
    XChartSpace();
    break;
  case MODEE:
    XChartEphemeris();
    break;
  case MODEW:
    DrawWorld(degree);                         /* First draw map of world. */
    if (xbonus && (exdisplay & DASHXW0) == 0)  /* Then maybe Ley lines.    */
      DrawLeyLines(degree);
    break;
  case MODEG:
  case MODEP:
    DrawGlobe(degree);
    break;
#ifdef BIORHYTHM
  case MODEb:
    XChartBiorhythm();
    break;
#endif
  }

  /* Print text showing chart information at bottom of window. */

  DrawColor(hilite);
  if (xtext && modex != MODEW && modex != MODEG && modex != MODEP &&
    ((modex != MODEv && modex != MODEw) || (exdisplay & DASHv0) > 0)) {
    if (Mon == -1)
      sprintf(string, "(no time or space)");
    else if (relation == DASHrc)
      sprintf(string, "(composite)");
    else {
      i = (int) (FRACT(dabs(Tim))*100.0+ROUND/60.0);
      j = (int) (FRACT(dabs(Zon))*100.0+ROUND/60.0);
      k = ansi; ansi = FALSE;
      sprintf(string, "%s %s (%d:%02d GMT) %s",
        CharDate(Mon, Day, Yea, 2), CharTime((int)floor(Tim), i),
        (int)(-Zon), j, CharLocation(Lon, Lat, 100.0));
      ansi = k;
    }
    DrawText(string, chartx/2, charty-3*scalet, TRUE);
  }

  /* Draw a border around the chart if the mode is set and appropriate. */

  if ((xborder || modex == MODEg) && modex != MODEG && modex != MODEP &&
    (modex != MODEW || (exdisplay & DASHXW0) == 0))
    DrawEdgeAll();
}
#endif /* GRAPH */

/* xoptions.c */
