/* ** Astrolog (Version 4.40) File: xcharts2.c ** ** IMPORTANT NOTICE: The graphics database and chart display routines ** used in this program are Copyright (C) 1991-1995 by Walter D. Pullen ** (astara@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 1/29/1995. */ #include "astrolog.h" #ifdef GRAPH /* ****************************************************************************** ** Chart Graphics Utility Procedures. ****************************************************************************** */ /* 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. */ bool FProper(i) int i; { bool f; if (gi.nMode == gHorizon || gi.nMode == gEphemeris || fMap || gi.nMode == gGlobe || gi.nMode == gPolar) /* Astro-graph / ephem charts */ f = FObject(i); else if (gi.nMode == gOrbit) /* Solar system charts */ f = FObject(i) && (i != oMoo || (us.fPlacalc && us.objCenter < oMoo)); else f = fTrue; return f && !ignore[i]; /* Check restriction status */ } /* 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)gs.yWin*(real)gi.nScale, 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 < us.nDivision*2; l++) { k = 0; for (i = 1; i <= cObj; i++) if (FProper(i)) { /* For each object, determine who is closest on either side. */ k1 = rLarge; k2 = -rLarge; for (j = 1; j <= cObj; j++) if (FProper(j) && i != j) { temp = symbol[j]-symbol[i]; if (RAbs(temp) > rDegHalf) temp -= rDegMax*RSgn(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)gi.nScale, max = rDegMax, k1, k2, temp; int i, j, k = 1, l; if (gi.nMode != gEphemeris) max *= (real)gi.nScale; else orb *= rDegMax/(real)gs.xWin; /* Keep adjusting as long as we can still make changes. */ for (l = 0; k && l < us.nDivision*2; l++) { k = 0; for (i = 1; i <= cObj*2; i++) if (FProper((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 <= cObj*2; j++) { if (FProper((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; } } } } /* 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 HousePlaceInX(deg) real deg; { int in; if (gi.nMode == gWheel) /* We only adjust for the -w -X combination. */ return deg; in = HousePlaceIn(deg); return Mod(ZFromS(in)+MinDistance(house[in], deg)/ MinDistance(house[in], house[Mod12(in+1)])*30.0); } /* ****************************************************************************** ** Multiple Chart Graphics Routines. ****************************************************************************** */ /* 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[cSign+1], xhouse1[cSign+1], xplanet1[objMax], xplanet2[objMax], symbol[objMax]; 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 (gs.fText && !us.fVelocity) gs.xWin -= xSideT; cx = gs.xWin/2 - 1; cy = gs.yWin/2 - 1; unitx = (real)cx; unity = (real)cy; asc = gs.nLeft ? cp1.obj[abs(gs.nLeft)]+90*(gs.nLeft < 0) : cp1.cusp[1]; /* Fill out arrays with the degree of each object, cusp, and sign glyph. */ if (gi.nMode == gWheel) { for (i = 1; i <= cSign; i++) xhouse1[i] = PZ(cp1.cusp[i]); } else { asc -= cp1.cusp[1]; for (i = 1; i <= cSign; i++) xhouse1[i] = PZ(ZFromS(i)); } for (i = 1; i <= cSign; i++) xsign[i] = PZ(HousePlaceInX(ZFromS(i))); for (i = 1; i <= cObj; i++) xplanet1[i] = PZ(HousePlaceInX(cp1.obj[i])); for (i = 1; i <= cObj; i++) xplanet2[i] = PZ(HousePlaceInX(cp2.obj[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(gi.kiLite); DrawDash(cx+POINT1(unitx, 0.99, PX(xhouse1[sAri])), cy+POINT1(unity, 0.99, PY(xhouse1[sAri])), cx+POINT1(unitx, 0.99, PX(xhouse1[sLib])), cy+POINT1(unity, 0.99, PY(xhouse1[sLib])), !gs.fColor); DrawDash(cx+POINT1(unitx, 0.99, PX(xhouse1[sCap])), cy+POINT1(unity, 0.99, PY(xhouse1[sCap])), cx+POINT1(unitx, 0.99, PX(xhouse1[sCan])), cy+POINT1(unity, 0.99, PY(xhouse1[sCan])), !gs.fColor); for (i = 0; i < nDegMax; i += 5-(gs.fColor || gs.fPS || gs.fMeta)*4) { temp = PZ(HousePlaceInX((real)i)); px = PX(temp); py = PY(temp); DrawColor(i%5 ? gi.kiGray : gi.kiOn); DrawDash(cx+POINT1(unitx, 0.78, px), cy+POINT1(unity, 0.78, py), cx+POINT2(unitx, 0.82, px), cy+POINT2(unity, 0.82, py), ((gs.fPS || gs.fMeta) && i%5)*2); } DrawColor(gi.kiOn); DrawCircle(cx, cy, (int)(unitx*0.95+rRound), (int)(unity*0.95+rRound)); DrawCircle(cx, cy, (int)(unitx*0.82+rRound), (int)(unity*0.82+rRound)); DrawCircle(cx, cy, (int)(unitx*0.78+rRound), (int)(unity*0.78+rRound)); DrawCircle(cx, cy, (int)(unitx*0.70+rRound), (int)(unity*0.70+rRound)); for (i = 1; i <= cSign; i++) { temp = xsign[i]; DrawColor(gi.kiOn); DrawLine(cx+POINT2(unitx, 0.95, PX(temp)), cy+POINT2(unity, 0.95, PY(temp)), cx+POINT1(unitx, 0.82, PX(temp)), cy+POINT1(unity, 0.82, PY(temp))); DrawLine(cx+POINT2(unitx, 0.78, PX(xhouse1[i])), cy+POINT2(unity, 0.78, PY(xhouse1[i])), cx+POINT1(unitx, 0.70, PX(xhouse1[i])), cy+POINT1(unity, 0.70, PY(xhouse1[i]))); if (gs.fColor && i%3 != 1) { DrawColor(gi.kiGray); DrawDash(cx, cy, cx+POINT1(unitx, 0.70, PX(xhouse1[i])), cy+POINT1(unity, 0.70, PY(xhouse1[i])), 1); } temp = Midpoint(temp, xsign[Mod12(i+1)]); DrawColor(kSignB(i)); DrawSign(i, cx+POINT1(unitx, 0.885, PX(temp)), cy+POINT1(unity, 0.885, PY(temp))); temp = Midpoint(xhouse1[i], xhouse1[Mod12(i+1)]); DrawHouse(i, cx+POINT1(unitx, 0.74, PX(temp)), cy+POINT1(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 <= cObj; i++) symbol[i] = xplanet2[i]; FillSymbolRing(symbol); for (i = cObj; i >= 1; i--) if (FProper2(i)) { if (gs.fLabel) { temp = symbol[i]; DrawColor(cp2.dir[i] < 0.0 ? gi.kiGray : gi.kiOn); DrawDash(cx+POINT1(unitx, 0.58, PX(xplanet2[i])), cy+POINT1(unity, 0.58, PY(xplanet2[i])), cx+POINT2(unitx, 0.61, PX(temp)), cy+POINT2(unity, 0.61, PY(temp)), (cp2.dir[i] < 0.0 ? 1 : 0) - gs.fColor); DrawObject(i, cx+POINT1(unitx, 0.65, PX(temp)), cy+POINT1(unity, 0.65, PY(temp))); } DrawColor(kObjB[i]); DrawPoint(cx+POINT1(unitx, 0.56, PX(xplanet2[i])), cy+POINT1(unity, 0.56, PY(xplanet2[i]))); DrawPoint(cx+POINT1(unitx, 0.43, PX(xplanet2[i])), cy+POINT1(unity, 0.43, PY(xplanet2[i]))); DrawColor(cp2.dir[i] < 0.0 ? gi.kiGray : gi.kiOn); DrawDash(cx+POINT1(unitx, 0.45, PX(xplanet2[i])), cy+POINT1(unity, 0.45, PY(xplanet2[i])), cx+POINT2(unitx, 0.54, PX(xplanet2[i])), cy+POINT2(unity, 0.54, PY(xplanet2[i])), 2-gs.fColor); } /* 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 <= cObj; i++) symbol[i] = xplanet1[i]; FillSymbolRing(symbol); for (i = 1; i <= cObj; i++) if (FProper(i)) { if (gs.fLabel) { temp = symbol[i]; DrawColor(cp1.dir[i] < 0.0 ? gi.kiGray : gi.kiOn); DrawDash(cx+POINT1(unitx, 0.45, PX(xplanet1[i])), cy+POINT1(unity, 0.45, PY(xplanet1[i])), cx+POINT2(unitx, 0.48, PX(temp)), cy+POINT2(unity, 0.48, PY(temp)), (cp1.dir[i] < 0.0 ? 1 : 0) - gs.fColor); DrawObject(i, cx+POINT1(unitx, 0.52, PX(temp)), cy+POINT1(unity, 0.52, PY(temp))); } else DrawColor(kObjB[i]); DrawPoint(cx+POINT1(unitx, 0.43, PX(xplanet1[i])), cy+POINT1(unity, 0.43, PY(xplanet1[i]))); } /* Draw lines connecting planets between the two charts that have aspects. */ if (!gs.fAlt) { /* Don't draw aspects in bonus mode. */ if (!FCreateGridRelation(fFalse)) return; for (j = cObj; j >= 1; j--) for (i = cObj; i >= 1; i--) if (grid->n[i][j] && FProper2(i) && FProper(j)) { DrawColor(kAspB[grid->n[i][j]]); DrawDash(cx+POINT1(unitx, 0.41, PX(xplanet1[j])), cy+POINT1(unity, 0.41, PY(xplanet1[j])), cx+POINT1(unitx, 0.41, PX(xplanet2[i])), cy+POINT1(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 sz[cchSzDef]; int unit, siz, x, y, i, j, k, l; KI c; unit = CELLSIZE*gi.nScale; siz = (gs.nGridCell+1)*unit; if (!FCreateGridRelation(gs.fAlt != us.fGridConfig)) return; for (y = 0, j = -1; y <= gs.nGridCell; y++) { do { j++; } while (ignore[j] && j <= cObj); DrawColor(gi.kiGray); DrawDash(0, (y+1)*unit, siz, (y+1)*unit, !gs.fColor); DrawDash((y+1)*unit, 0, (y+1)*unit, siz, !gs.fColor); DrawColor(gi.kiLite); DrawEdge(0, y*unit, unit, (y+1)*unit); DrawEdge(y*unit, 0, (y+1)*unit, unit); if (j <= cObj) for (x = 0, i = -1; x <= gs.nGridCell; x++) { do { i++; } while (ignore[i] && i <= cObj); /* Again, we are looping through each cell in each row and column. */ if (i <= cObj) { gi.xTurtle = x*unit+unit/2; gi.yTurtle = y*unit+unit/2 - (gi.nScale/gi.nScaleT > 2 ? 5*gi.nScaleT : 0); k = grid->n[i][j]; /* 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, gi.xTurtle, gi.yTurtle); } else { /* Otherwise, draw glyph of aspect in effect, or glyph of */ /* sign of midpoint, between the two planets in question. */ if (gs.fAlt == us.fGridConfig) { if (k) { DrawColor(c = kAspB[k]); DrawAspect(k, gi.xTurtle, gi.yTurtle); } } else { DrawColor(c = kSignB(grid->n[i][j])); DrawSign(grid->n[i][j], gi.xTurtle, gi.yTurtle); } } /* Again, when scale size is 300+, print some text in current cell: */ if (gi.nScale/gi.nScaleT > 2 && gs.fLabel) { /* For top and left edges, print sign and degree of the planet. */ if (y == 0 || x == 0) { if (x+y > 0) { k = SFromZ(y == 0 ? cp2.obj[i] : cp1.obj[j]); l = (int)((y == 0 ? cp2.obj[i] : cp1.obj[j])-ZFromS(k)); c = kSignB(k); sprintf(sz, "%c%c%c %02d", chSig3(k), l); /* For extreme upper left corner, print some little arrows */ /* pointing out chart1's planets and chart2's planets. */ } else { c = gi.kiLite; sprintf(sz, "1v 2->"); } } else { k = abs(grid->v[i][j]); /* For aspect cells, print the orb in degrees and minutes. */ if (gs.fAlt == us.fGridConfig) { if (grid->n[i][j]) sprintf(sz, "%c%d %02d'", k != grid->v[i][j] ? (us.fAppSep ? 'a' : '-') : (us.fAppSep ? 's' : '+'), k/60, k%60); else sprintf(sz, ""); /* For midpoint cells, print degree and minute. */ } else sprintf(sz, "%2d %02d'", k/60, k%60); } DrawColor(c); DrawSz(sz, x*unit+unit/2, (y+1)*unit-3*gi.nScaleT, dtBottom); } } } } } #ifdef BIORHYTHM /* 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 sz[6], *c; real jd, r, a; int x1, x2, xs, cx, y1, y2, ys, cy, i, j, k, x, y, x0, y0; k = xFont*6*gi.nScaleT; x1 = k; x2 = gs.xWin-k; xs = x2-x1; cx = (x1+x2)/2; k = CELLSIZE; y1 = k; y2 = gs.yWin-k; ys = y2-y1; cy = (y1+y2)/2; /* Create a dotted day/percentage grid to graph on. */ DrawColor(gi.kiGray); DrawDash(x1, cy, x2, cy, 1); DrawDash(cx, y1, cx, y2, 1); for (j = -BIODAYS+1; j <= BIODAYS-1; j++) { x = x1 + NMultDiv(xs, j+BIODAYS, BIODAYS*2); for (k = -90; k <= 90; k += 10) { y = y1 + NMultDiv(ys, 100+k, 200); DrawPoint(x, y); } } /* Now actually draw the three biorhythm curves. */ for (i = 1; i <= 3; i++) { jd = RFloor(is.JD + rRound); switch (i) { case 1: r = brPhy; c = "PHYS"; j = eFir; break; case 2: r = brEmo; c = "EMOT"; j = eWat; break; case 3: r = brInt; c = "INTE"; j = eEar; break; } DrawColor(kElemB[j]); for (jd -= (real)BIODAYS, j = -BIODAYS; j <= BIODAYS; j++, jd += 1.0) { a = RBiorhythm(jd, r); x = x1 + NMultDiv(xs, j+BIODAYS, BIODAYS*2); y = y1 + (int)((real)ys * (100.0-a) / 200.0); if (j > -BIODAYS) DrawLine(x0, y0, x, y); else DrawSz(c, x1/2, y+2*gi.nScaleT, dtCent); x0 = x; y0 = y; } } DrawColor(gi.kiLite); /* Label biorhythm percentages along right vertical axis. */ for (k = -100; k <= 100; k += 10) { sprintf(sz, "%c%3d%%", k < 0 ? '-' : '+', abs(k)); y = y1 + NMultDiv(ys, 100-k, 200); DrawSz(sz, (x2+gs.xWin)/2, y+2*gi.nScaleT, dtCent); } /* Label days on top horizontal axis. */ for (j = -BIODAYS+2; j < BIODAYS; j += 2) { x = x1 + NMultDiv(xs, j+BIODAYS, BIODAYS*2); sprintf(sz, "%c%d", j < 0 ? '-' : '+', abs(j)); DrawSz(sz, x, y1-2*gi.nScaleT, dtBottom); } DrawEdge(x1, y1, x2, y2); } #endif /* BIORHYTHM */ #endif /* GRAPH */ /* xcharts2.c */