Source code: jplot/Graph_Piper.java
1 /*
2 * JPLOT -- Java Plotting Interface for any programme or
3 * as an independent GUI
4 *
5 * Copyright (C) 1999 Jan van der Lee
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 * 02111-1307, USA.
21 *
22 * Send bugs, suggestions or queries to <jplot@cig.ensmp.fr>
23 * The latest releases are found at
24 * http://www.cig.ensmp.fr/~vanderlee/jplot.html
25 *
26 * Initally developed for use by the Centre d'Informatique Geologique
27 * Ecole des Mines de Paris, Fontainebleau, France.
28 */
29
30 package jplot;
31
32 import javax.swing.*;
33 import java.awt.*;
34 import java.awt.image.*;
35 import java.text.*;
36 import java.math.*;
37 import java.awt.geom.*;
38 import java.awt.event.*;
39 import javax.swing.border.*;
40 import java.util.*;
41 import java.awt.print.*;
42
43 /**
44 * The <code>Graph_Piper</code> class builds a panel which displays a
45 * Piper-diagram (ask geochemists what this is) according to the data
46 * stored in the {@link DataArray}.
47 * All general graph stuff and initialization is done in class
48 * <code>Graph</code>.
49 *
50 * @version 24/08/99
51 * @author J. van der Lee
52 */
53 public class Graph_Piper extends Graph {
54
55 // handy variables
56 private final double sin30 = Math.sin(Math.PI/6.0);
57
58 /**
59 * Main constructor.
60 * @param jp current jplot instance
61 * @param gp objects containing the parameters used to draw this graph.
62 */
63 public Graph_Piper(JPlot jp, GraphPars gp) {
64 super(jp,gp);
65 if (JPlot.debug) System.out.print("Initializing piper diagram...");
66 piperSep = 20.0;
67 gp.setBoxOffset(10.0f);
68 gp.setShowBox(true);
69 normalSep = piperSep*Math.sin(Math.PI/3);
70 gp.setMaxValue(X,100);
71 gp.setMinValue(X,0);
72 gp.setMaxValue(Y,100);
73 gp.setMinValue(Y,0);
74 numberOfTics[X] = 6;
75 numberOfTics[Y] = 6;
76 GraphLabel gl = new GraphLabel(GraphLabel.PIPER_X1,"Ca[2+]",
77 gp.getTicFont(X),Color.black);
78 gp.addLabel(gl);
79 gl = new GraphLabel(GraphLabel.PIPER_X2,"Cl[-]",
80 gp.getTicFont(X),Color.black);
81 gp.addLabel(gl);
82 gl = new GraphLabel(GraphLabel.PIPER_Y1,"Mg[2+]",
83 gp.getTicFont(Y),Color.black);
84 gp.addLabel(gl);
85 gl = new GraphLabel(GraphLabel.PIPER_Y2,"SO4[2-]",
86 gp.getTicFont(Y),Color.black);
87 gp.addLabel(gl);
88 gl = new GraphLabel(GraphLabel.PIPER_Y3,"Cl[-] + SO4[2-]",
89 gp.getTicFont(Y),Color.black);
90 gp.addLabel(gl);
91 gl = new GraphLabel(GraphLabel.PIPER_Y4,"Ca[2+] + Mg[2+]",
92 gp.getTicFont(Y),Color.black);
93 gp.addLabel(gl);
94 if (JPlot.debug) System.out.println("done.");
95 }
96
97 /*
98 * Makes a set of defaults labels which are set around the axes.
99 */
100 private void makeDefaultLabels() {
101 }
102
103 /**
104 * Determine the axis labels, if needed. These are numbers
105 * which are translated in Strings. The length is evalulated
106 * and used to set the left and bottom margins.
107 */
108 protected void makeTicLabels() {
109 double f, Xn, Yn;
110 final int numDigits = 4;
111
112 // make the X-axis labels:
113 //------------------------
114 if (gp.drawTicLabels(X)) {
115 Xn = (gp.getMaxValue(X)-gp.getMinValue(X))/(numberOfTics[X]-1);
116 if (JPlot.debug) System.out.print("\nXlabels:");
117 for (int i=0; i<numberOfTics[X]; i++) {
118 f = gp.getMinValue(X)+i*Xn;
119 if (JPlot.debug) System.out.print(" " + f);
120 ticLabel[X][i] = formatNumber(f,numDigits);
121 }
122 }
123
124 // make the Y axes labels:
125 //------------------------
126 if (gp.drawTicLabels(Y)) {
127 Yn = (gp.getMaxValue(Y)-gp.getMinValue(Y))/(numberOfTics[Y]-1);
128 if (JPlot.debug) System.out.print("\nYlabels:");
129 for (int i=0; i<numberOfTics[Y]; i++) {
130 f = gp.getMinValue(Y)+Yn*i;
131 if (JPlot.debug) System.out.print(" " + f);
132 ticLabel[Y][i] = formatNumber(f,numDigits);
133 }
134 if (JPlot.debug) System.out.println();
135 }
136 }
137
138 /**
139 * This function draws the tics on the axis.
140 * Quite complex job for piper diagrams...
141 * @param g2 instance of the graphical canvas
142 */
143 private void drawTics(Graphics2D g2) {
144 double x2,y2,x1, y1 = topMargin + axisLength[Y];
145
146 // draw the X-tics in the lower two triangles:
147 if (gp.drawTics(X)) {
148 for (int i=1; i<numberOfTics[X]-1; i++) {
149 x1 = leftMargin + sep[X]*i;
150 x2 = x1-cos*ticLength[X];
151 y2 = y1-sin*ticLength[X];
152 line.setLine(x1,y1,x2,y2);
153 g2.draw(line);
154 x1 += triangleBottom+piperSep;
155 x2 = x1+cos*ticLength[X];
156 y2 = y1-sin*ticLength[X];
157 line.setLine(x1,y1,x2,y2);
158 g2.draw(line);
159 }
160 }
161 // draw the Y-tics in the lower two triangles:
162 if (gp.drawTics(Y)) {
163 y1 = topMargin + axisLength[Y];
164 for (int i=1; i<numberOfTics[Y]-1; i++) {
165 x1 = leftMargin + sep[X]*i/2.0;
166 x2 = x1 + ticLength[Y];
167 y1 -= sep[Y];
168 line.setLine(x1,y1,x2,y1);
169 g2.draw(line);
170 x1 = leftMargin + axisLength[X] - sep[X]*i/2.0;
171 x2 = x1 - ticLength[Y];
172 line.setLine(x1,y1,x2,y1);
173 g2.draw(line);
174 }
175 }
176 }
177
178 /**
179 * This function draws the grid.
180 * @param g2 instance of the graphical canvas
181 */
182 private void drawGrid(Graphics2D g2) {
183 double x2,y2,x1, y1 = topMargin + axisLength[Y];
184 g2.setColor(gp.getGridColor());
185
186 // draw the X-grid lines in the lower two triangles:
187 if (gp.drawGrid(X) || gp.drawGrid(Y)) {
188 for (int j=0; j<2; j++) {
189 double offset = (triangleBottom+piperSep)*j + leftMargin;
190 for (int i=1; i<numberOfTics[X]-1; i++) {
191 x1 = offset + sep[X]*i;
192 x2 = x1+(triangleBottom-sep[X]*i)/2.0;
193 y2 = y1-(triangleHeight-sep[Y]*i);
194 if (gp.drawGrid(X)) {
195 line.setLine(x1,y1,x2,y2);
196 g2.draw(line);
197 }
198 x1 = x2-sep[X]*i;
199 if (gp.drawGrid(Y)) {
200 line.setLine(x2,y2,x1,y2);
201 g2.draw(line);
202 }
203 x2 = offset + triangleBottom - sep[X]*i;
204 if (gp.drawGrid(X)) {
205 line.setLine(x1,y2,x2,y1);
206 g2.draw(line);
207 }
208 }
209 }
210 }
211
212 if (gp.drawGrid(X)) {
213 // draw the grid in the upper area:
214 double sign=1.0;
215 for (int j=0; j<2; j++) {
216 y1 = topMargin + axisLength[Y] - normalSep;
217 if (j == 1) sign = -1.0;
218 for (int i=1; i<numberOfTics[X]-1; i++) {
219 x1 = leftMargin + triangleBottom + piperSep/2 - sign*sin30*sep[X]*i;
220 //x1 = leftMargin + triangleBottom + piperSep/2 - sign*sin*sep[X]*i;
221 y1 -= sep[Y];
222 x2 = x1 + sign*triangleBottom/2;
223 y2 = y1-triangleHeight;
224 line.setLine(x1,y1,x2,y2);
225 g2.draw(line);
226 }
227 }
228 }
229 }
230
231 /*
232 * Draws a colored, filled triangle in each triangle.
233 * Used to quickly check whether the points are centered.
234 */
235 private void drawFilledTriangles(Graphics2D g2) {
236 double xOffset;
237 // first we build the two triangles at the bottom
238 g2.setColor(gp.getInnerColor());
239 for (int i=0; i<2; i++) {
240 xOffset = leftMargin+i*(axisLength[X]+piperSep)/2.0;
241 Polygon p = new Polygon();
242 p.addPoint((int)(xOffset+triangleBottom/4.0),
243 (int)(topMargin+axisLength[Y]-triangleHeight/2.0));
244 p.addPoint((int)(xOffset+3.0*triangleBottom/4.0),
245 (int)(topMargin+axisLength[Y]-triangleHeight/2.0));
246 p.addPoint((int)(xOffset+triangleBottom/2.0),
247 (int)(topMargin+axisLength[Y]));
248 g2.fill(p);
249 }
250 // next, the triangles in the upper one
251 xOffset = leftMargin+(axisLength[X]-triangleBottom/2.0)/2.0;
252 Polygon p = new Polygon();
253 p.addPoint((int)(xOffset),
254 (int)(topMargin+axisLength[Y]-triangleHeight/2.0-normalSep));
255 p.addPoint((int)(xOffset+triangleBottom/2.0),
256 (int)(topMargin+axisLength[Y]-triangleHeight/2.0-normalSep));
257 p.addPoint((int)(xOffset),
258 (int)(topMargin+triangleHeight/2.0));
259 p.addPoint((int)(xOffset+triangleBottom/2.0),
260 (int)(topMargin+triangleHeight/2.0));
261 g2.fill(p);
262 }
263
264 protected boolean setMinMax(int axis, Vector data) {
265 return true;
266 }
267
268 /*
269 * This function draws the axes (actually X,Y and mirror axes) and
270 * the tics on the axes.
271 * @param g2 instance of a graphical canvas on which to draw the axes
272 * @param background true if this function should draw only the
273 * background color of the axis system.
274 */
275 private void plotAxes(Graphics2D g2, boolean background) {
276 // draw the bounding box:
277 double x = leftMargin;
278 double y = axisLength[Y] + topMargin;
279
280 // draw triangles:
281 for (int i=0; i<2; i++) {
282 Polygon p = new Polygon();
283 int x1 = (int)(x + (triangleBottom + piperSep)*i);
284 p.addPoint(x1,(int)y);
285 p.addPoint(x1 + (int)triangleBottom,(int)y);
286 p.addPoint(x1 + (int)triangleBottom/2,(int)(y-triangleHeight));
287 if (background) {
288 g2.setColor(gp.getGraphBackgroundColor());
289 g2.fill(p);
290 }
291 else {
292 g2.setColor(gp.getAxesColor());
293 g2.draw(p);
294 }
295 }
296 Polygon p = new Polygon();
297 p.addPoint((int)(x+triangleBottom+piperSep/2),(int)(y-normalSep));
298 p.addPoint((int)(x+(triangleBottom+piperSep)/2),
299 (int)(y-triangleHeight-normalSep));
300 p.addPoint((int)(x+triangleBottom+piperSep/2),
301 (int)(topMargin));
302 p.addPoint((int)(x+(3*triangleBottom+piperSep)/2),
303 (int)(y-triangleHeight-normalSep));
304 if (background) {
305 g2.setColor(gp.getGraphBackgroundColor());
306 g2.fill(p);
307 }
308 else {
309 g2.setColor(gp.getAxesColor());
310 g2.draw(p);
311 drawTics(g2);
312 }
313 }
314
315 /*
316 * Draw the labels around the x- and y-tics. The xLabelHeight is determined
317 * as a function of one label-height (assuming the height is the same for
318 * all the labels).
319 * @param g2 instance of a 2D graphical canvas
320 */
321 private void plotTicLabels(Graphics2D g2) {
322 float x, y;
323 double a = topMargin + axisLength[Y];
324 double f;
325
326 // draw the X-axis labels:
327 double xOffset = leftMargin;
328 int k;
329 if (gp.drawTicLabels(X)) {
330 FontMetrics fm = getFontMetrics(gp.getTicFont(X));
331 double xLabelHeight = fm.getHeight();
332 g2.setColor(gp.getTicColor(X));
333 g2.setFont(gp.getTicFont(X));
334 for (int j=0; j<2; j++) {
335 if (j>0) xOffset += triangleBottom + piperSep;
336 for (int i=0; i<numberOfTics[X]; i++) {
337 if (j > 0) k = i;
338 else k = numberOfTics[X]-1 - i;
339 x = (float)(xOffset+sep[X]*i-fm.stringWidth(ticLabel[X][k])/2);
340 y = (float)(a+xLabelHeight);
341 g2.drawString(ticLabel[X][k],x,y);
342 }
343 }
344 }
345
346 // draw the Y axes labels:
347 if (gp.drawTicLabels(Y)) {
348 FontMetrics fm = getFontMetrics(gp.getTicFont(Y));
349 double yLabelHeight = fm.getHeight();
350 g2.setColor(gp.getTicColor(Y));
351 g2.setFont(gp.getTicFont(Y));
352 for (k=0; k<2; k++) {
353 double yOffset=k*(triangleHeight + normalSep);
354 xOffset = leftMargin + k*(triangleBottom+piperSep)/2.0;
355 double sign=1.0;
356 for (int j=0; j<2; j++) {
357 if (j == 1) {
358 sign = -1.0;
359 xOffset = leftMargin + axisLength[X] +
360 k*sign*(triangleBottom+piperSep)/2.0;
361 }
362 for (int i=0; i<numberOfTics[Y]; i++) {
363 x = (float)(xOffset+(j-1)*
364 fm.stringWidth(ticLabel[Y][i])-sign*xSep);
365 x += sign*sep[X]*i/2;
366 y = (float)(a-sep[Y]*i+yLabelHeight/4 - yOffset);
367 g2.drawString(ticLabel[Y][i],x,y);
368 }
369 }
370 }
371 }
372 }
373
374 // dummy instance
375 protected double toX(double x) {
376 return x;
377 }
378
379 /**
380 * Returns the X-value scaled to the pixel-availability.
381 * This function takes the X-value and returns the corresponding
382 * coordinates for the panel.
383 * @param x real x-value (as introduced by the data)
384 * @param y y-value in pixels
385 * @return x-value scaled to the actual panel coordinates
386 */
387 protected double toX(double x, double y) {
388 return leftMargin-triangleHeight*(y/gp.getMaxValue(Y))/tan +
389 (gp.getMaxValue(X)-x)*triangleBottom/gp.getMaxValue(X);
390 }
391
392 /**
393 * Returns the X-value scaled to the pixel-availability.
394 * This function takes the X-value and returns the corresponding
395 * coordinates for the panel.
396 * @param x real x-value (as introduced by the data)
397 * @param y y-value in pixels
398 * @return x-value scaled to the actual panel coordinates
399 */
400 protected double toX2(double x, double y) {
401 return leftMargin + axisLength[X] -
402 triangleBottom + triangleHeight*(y/gp.getMaxValue(Y))/tan +
403 x*triangleBottom/gp.getMaxValue(X);
404 }
405
406 /**
407 * Returns the X-value scaled to the pixel-availability.
408 * This function takes the X-value and returns the corresponding
409 * coordinates the upper triangles.
410 * @param x real x-value (as introduced by the data)
411 * @param y y-value in pixels
412 * @return x-value scaled to the actual panel coordinates
413 */
414 protected double toX3(double x, double y) {
415 return leftMargin + axisLength[X]/2.0 +
416 cos*(y-x)*triangleSide/gp.getMaxValue(X);
417 }
418
419 /**
420 * Returns the Y-value scaled to the pixel-availability.
421 * This function takes the Y-value and returns the corresponding
422 * coordinates for the lower triangles.
423 * @param y real y-value (as introduced by the data)
424 * @return y-value scaled to the actual panel coordinates
425 */
426 protected double toY(double y) {
427 return topMargin + axisLength[Y] - triangleHeight*(y/gp.getMaxValue(Y));
428 }
429
430 /**
431 * Returns the Y-value scaled to the pixel-availability.
432 * This function takes the Y-value and returns the corresponding
433 * coordinates for the upper part of the graph.
434 * @param y real y-value (as introduced by the data)
435 * @return y-value scaled to the actual panel coordinates
436 */
437 protected double toY3(double x, double y) {
438 return topMargin + axisLength[Y] - normalSep -
439 sin*(x+y)*triangleSide/gp.getMaxValue(Y);
440 }
441
442 /**
443 * Fills the graph area with a background color. The
444 * area is the area between the axes.
445 * @param g2 graphics canvas
446 */
447 protected void fillGraphArea(Graphics2D g2) {
448 plotAxes(g2,true);
449 }
450
451 /**
452 * This function builds the graph in a double-buffered image zone.
453 */
454 protected void updateGraph() {
455 Graphics2D g2 = createGraphics();
456 double x, y;
457 double a = topMargin + axisLength[Y];
458
459 // draw the on the background, if this is what they want:
460 if (gp.showInner()) drawFilledTriangles(g2);
461 g2.setStroke(new BasicStroke());
462 if (!gp.gridToFront()) drawGrid(g2);
463
464 int i;
465 for (Enumeration e=data.elements(); e.hasMoreElements();) {
466
467 // get the next data array from the vector:
468 DataArray da = (DataArray)e.nextElement();
469
470 // set color and penwidth:
471 g2.setColor(da.getColor());
472 g2.setStroke(da.getStroke());
473 x = toX(da.getX(0),da.getY(0));
474 y = toY(da.getY(0));
475 drawPointType(da.getSymbol(),g2,x,y,da.getSymbolSize());
476 x = toX2(da.getX(1),da.getY(1));
477 y = toY(da.getY(1));
478 drawPointType(da.getSymbol(),g2,x,y,da.getSymbolSize());
479 x = toX3(da.getX(0)+da.getY(0),da.getX(1)+da.getY(1));
480 y = toY3(da.getX(0)+da.getY(0),da.getX(1)+da.getY(1));
481 drawPointType(da.getSymbol(),g2,x,y,da.getSymbolSize());
482 if (gp.drawTds() && da.size() > 2) {
483 g2.setStroke(new BasicStroke());
484 drawPointType(0,g2,x,y,(float)(gp.getTdsFactor()*
485 da.getSymbolSize()*da.getX(2)));
486 }
487 }
488 g2.setStroke(new BasicStroke());
489
490 // draw the on the foreground, if this is what they want:
491 if (gp.gridToFront()) drawGrid(g2);
492
493 // draw the axes and tics:
494 plotAxes(g2,false);
495
496 // draw the tic labels:
497 plotTicLabels(g2);
498
499 // plot the labels:
500 plotLabels(g2);
501
502 // draw the legend. Do this at the end because it must be
503 // drawn on the top of all other drawing stuff (e.g. grid...)
504 if (gp.drawLegend()) {
505 int k=0;
506 g2.setFont(gp.getLegendFont());
507 for (Enumeration e=data.elements(); e.hasMoreElements(); k++) {
508 DataArray da = (DataArray)e.nextElement();
509 if (!da.drawLegend()) k--;
510 else {
511 g2.setColor(da.getColor());
512 if (da.drawLine()) g2.setStroke(da.getStroke());
513 drawLegend(g2,da,k);
514 }
515 }
516 }
517 g2.setStroke(new BasicStroke());
518 repaint();
519 }
520
521 /**
522 * Checks whether x and y are within the ranges. The ranges are
523 * defined by the axes system.
524 * @param x x-point
525 * @param y y-point
526 */
527 private boolean inRange(double x, double y) {
528 if (x < leftMargin || x > leftMargin + axisLength[X] ||
529 y < topMargin || y > topMargin + axisLength[Y]) return false;
530 return true;
531 }
532 }