-
Notifications
You must be signed in to change notification settings - Fork 224
/
Plot.java
4440 lines (4134 loc) · 183 KB
/
Plot.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package ij.gui;
import java.awt.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.Method;
import java.awt.geom.Point2D;
import ij.*;
import ij.process.*;
import ij.util.*;
import ij.plugin.Colors;
import ij.plugin.filter.Analyzer;
import ij.macro.Interpreter;
import ij.measure.Calibration;
import ij.measure.Measurements;
import ij.measure.ResultsTable;
/** This class creates an image that line graphs, scatter plots and plots of vector fields
* (arrows) can be drawn on and displayed.
*
* Note that the clone() operation is a shallow clone: objects like arrays, the PlotProperties,
* PlotObjects, the ImagePlus etc. of the clone remain the same as those of the original.
*
* @author Wayne Rasband
* @author Philippe CARL, CNRS, philippe.carl (AT) unistra.fr (log axes, arrows, ArrayList data)
* @author Norbert Vischer (overlay range arrows, 'R'eset range, filled plots, dynamic plots, boxes and whiskers, superscript)
* @author Michael Schmid (axis grid/ticks, resizing/panning/changing range, high-resolution, serialization)
*/
public class Plot implements Cloneable {
/** Text justification. */
public static final int LEFT=ImageProcessor.LEFT_JUSTIFY, CENTER=ImageProcessor.CENTER_JUSTIFY, RIGHT=ImageProcessor.RIGHT_JUSTIFY;
/** Legend positions */
//NOTE: These have only bits of LEGEND_POSITION_MASK set. The flags of the legend are stored as flags of the legend PlotObject.
//These are saved in the flags of the PlotObject class; thus bits up to 0x0f are reserved
public static final int TOP_LEFT=0x90, TOP_RIGHT=0xA0, BOTTOM_LEFT=0xB0, BOTTOM_RIGHT=0xC0, AUTO_POSITION=0x80;
/** Masks out bits for legend positions; if all these bits are off, the legend is turned off */
static final int LEGEND_POSITION_MASK = 0xf0;
/** Legend has its curves in bottom-to-top sequence (otherwise top to bottom) */
public static final int LEGEND_BOTTOM_UP = 0x100;
/** Legend erases background (otherwise transparent) */
public static final int LEGEND_TRANSPARENT = 0x200;
/** Display points using a circle (5 pixels in diameter if line thickness<=1, otherwise 7). */
public static final int CIRCLE = 0;
/** Display points using an X-shaped mark. */
public static final int X = 1;
/** Connect points with solid lines. */
public static final int LINE = 2;
/** Display points using a square box-shaped mark. */
public static final int BOX = 3;
/** Display points using an tiangular mark. */
public static final int TRIANGLE = 4;
/** Display points using an cross-shaped mark. */
public static final int CROSS = 5;
/** Display points using a single pixel. */
public static final int DOT = 6;
/** Draw black lines between the dots and a circle with the given color at each dot */
public static final int CONNECTED_CIRCLES = 7;
/** Display points using an diamond-shaped mark. */
public static final int DIAMOND = 8;
/** Draw shape using macro code */
public static final int CUSTOM = 9;
/** Fill area between line plot and x-axis at y=0. */
public static final int FILLED = 10;
/** Draw a histogram bar for each point (bars touch each other unless the x axis has categories set via the axis label.
* x values should be sorted (ascending or descending) */
public static final int BAR = 11;
/** Draw a free-standing bar for each point. x values should be equidistant and sorted (ascending or descending) */
public static final int SEPARATED_BAR = 12;
/** Names for the shapes as an array */
final static String[] SHAPE_NAMES = new String[] {
"Circle", "X", "Line", "Box", "Triangle", "+", "Dot", "Connected Circles", "Diamond",
"Custom", "Filled", "Bar", "Separated Bars"};
/** Names in nicely sorting order for menus */
final static String[] SORTED_SHAPES = new String[] {
SHAPE_NAMES[LINE], SHAPE_NAMES[CONNECTED_CIRCLES], SHAPE_NAMES[FILLED], SHAPE_NAMES[BAR], SHAPE_NAMES[SEPARATED_BAR],
SHAPE_NAMES[CIRCLE], SHAPE_NAMES[BOX], SHAPE_NAMES[TRIANGLE], SHAPE_NAMES[CROSS],
SHAPE_NAMES[DIAMOND], SHAPE_NAMES[X], SHAPE_NAMES[DOT]};
/** flag for numeric labels of x-axis ticks */
public static final int X_NUMBERS = 0x1;
/** flag for numeric labels of x-axis ticks */
public static final int Y_NUMBERS = 0x2;
/** flag for drawing major ticks on linear (non-logarithmic) x axis */
public static final int X_TICKS = 0x4;
/** flag for drawing major ticks on linear (non-logarithmic) y axis */
public static final int Y_TICKS = 0x8;
/** flag for drawing vertical grid lines for x axis */
public static final int X_GRID = 0x10;
/** flag for drawing horizontal grid lines for y axis */
public static final int Y_GRID = 0x20;
/** flag for forcing frame to coincide with the grid/ticks in x direction (results in unused space) */
public static final int X_FORCE2GRID = 0x40;
/** flag for forcing frame to coincide with the grid/ticks in y direction (results in unused space) */
public static final int Y_FORCE2GRID = 0x80;
/** flag for drawing minor ticks on linear (non-logarithmic) x axis */
public static final int X_MINOR_TICKS = 0x100;
/** flag for drawing minor ticks on linear (non-logarithmic) y axis */
public static final int Y_MINOR_TICKS = 0x200;
/** flag for logarithmic x-axis */
public static final int X_LOG_NUMBERS = 0x400;
/** flag for logarithmic y axis */
public static final int Y_LOG_NUMBERS = 0x800;
/** flag for ticks (major and minor, if space) on logarithmic x axis */
public static final int X_LOG_TICKS = 0x1000;
/** flag for ticks (major and minor, if space) on logarithmic y axis */
public static final int Y_LOG_TICKS = 0x2000;
//leave 0x4000, 0x8000 reserved for broken axes?
/** The default axisFlags, will be modified by PlotWindow.noGridLines and PlotWindow.noTicks (see getDefaultFlags) */
public static final int DEFAULT_FLAGS = X_NUMBERS + Y_NUMBERS + /*X_TICKS + Y_TICKS +*/
X_GRID + Y_GRID + X_LOG_TICKS + Y_LOG_TICKS;
/** Flag for addressing the x axis, for copying from a template: copy/write x axis range. */
// This must be 0x1 because bit shift operations are used for the other axes
public static final int X_RANGE = 0x1;
/** Flag for addressing the y axis, for copying from a template: copy/write y axis range */
public static final int Y_RANGE = 0x2;
/** Flags for modifying the range of all axes */
static final int ALL_AXES_RANGE = X_RANGE | Y_RANGE;
//0x4, 0x8 reserved for secondary axes
/** Flag for copying from a template: copy plot size */
public static final int COPY_SIZE = 0x10;
/** Flag for copying from a template: copy style & text of axis labels */
public static final int COPY_LABELS = 0x20;
/** Flag for copying from a template: copy legend */
public static final int COPY_LEGEND = 0x40;
/** Flag for copying from a template: copy axis style */
public static final int COPY_AXIS_STYLE = 0x80;
/** Flag for copying from a template: copy contents style */
public static final int COPY_CONTENTS_STYLE = 0x100;
/** Flag for copying PlotObjects (curves...) from a template if the template has more PlotObjects than the Plot to copy to. */
public static final int COPY_EXTRA_OBJECTS = 0x200;
/** The default margin width left of the plot frame (enough for 5-digit numbers such as unscaled 16-bit
* @deprecated Not a fixed value any more, use getDrawingFrame() to get the drawing area */
public static final int LEFT_MARGIN = 65;
/** The default margin width right of the plot frame
* @deprecated Not a fixed value any more, use getDrawingFrame() to get the drawing area */
public static final int RIGHT_MARGIN = 18;
/** The default margin width above the plot frame
* @deprecated Not a fixed value any more, use getDrawingFrame() to get the drawing area */
public static final int TOP_MARGIN = 15;
/** The default margin width below the plot frame
* @deprecated Not a fixed value any more, use getDrawingFrame() to get the drawing area */
public static final int BOTTOM_MARGIN = 40;
/** minimum width of frame area in plot */
public static final int MIN_FRAMEWIDTH = 160;
/** minimum width of frame area in plot */
public static final int MIN_FRAMEHEIGHT = 90;
/** key in ImagePlus properties to access the plot behind an ImagePlus */
public static final String PROPERTY_KEY = "thePlot";
static final float DEFAULT_FRAME_LINE_WIDTH = 1.0001f; //Frame thickness
private static final int MIN_X_GRIDSPACING = 45; //minimum distance between grid lines or ticks along x at plot width 0
private static final int MIN_Y_GRIDSPACING = 30; //minimum distance between grid lines or ticks along y at plot height 0
private final double MIN_LOG_RATIO = 3; //If max/min ratio is less than this, force linear axis even if log required. should be >2
private static final int LEGEND_PADDING = 4; //pixels around legend text etc
private static final int LEGEND_LINELENGTH = 20; //length of lines in legend
private static final int USUALLY_ENLARGE = 1, ALWAYS_ENLARGE = 2; //enlargeRange settings
private static final double RELATIVE_ARROWHEAD_SIZE = 0.2; //arrow heads have 1/5 of vector length
private static final int MIN_ARROWHEAD_LENGTH = 3;
private static final int MAX_ARROWHEAD_LENGTH = 20;
private static final String MULTIPLY_SYMBOL = "\u00B7"; //middot, default multiplication symbol for scientific notation. Use setOptions("msymbol=\\u00d7") for '×'
PlotProperties pp = new PlotProperties(); //size, range, formatting etc, for easy serialization
PlotProperties ppSnapshot; //copy for reverting
Vector<PlotObject> allPlotObjects = new Vector<PlotObject>(); //all curves, labels etc., also serialized for saving/reading
Vector<PlotObject> allPlotObjectsSnapshot; //copy for reverting
private PlotVirtualStack stack;
/** For high-resolution plots, everything will be scaled with this number. Otherwise, must be 1.0.
* (creating margins, saving PlotProperties etc only supports scale=1.0) */
float scale = 1.0f;
Rectangle frame = null; //the clip frame, do not use for image scale
//The following are the margin sizes actually used. They are modified for font size and also scaled for high-resolution plots
int leftMargin = LEFT_MARGIN, rightMargin = RIGHT_MARGIN, topMargin = TOP_MARGIN, bottomMargin = BOTTOM_MARGIN;
int frameWidth; //width corresponding to plot range; frame.width is larger by 1
int frameHeight; //height corresponding to plot range; frame.height is larger by 1
int preferredPlotWidth = PlotWindow.plotWidth; //default size of plot frame (not taking 'High-Resolution' scale factor into account)
int preferredPlotHeight = PlotWindow.plotHeight;
double xMin = Double.NaN, xMax, yMin, yMax; //current plot range, logarithm if log axis
double[] currentMinMax = new double[]{Double.NaN, 0, Double.NaN, 0}; //current plot range, xMin, xMax, yMin, yMax (values, not logarithm if log axis)
double[] defaultMinMax = new double[]{Double.NaN, 0, Double.NaN, 0}; //default plot range
double[] savedMinMax = new double[]{Double.NaN, 0, Double.NaN, 0}; //keeps previous range for revert
int[] enlargeRange; // whether to enlarge the range slightly to avoid values at the border (0=off, USUALLY_ENLARGE, ALWAYS_ENLARGE)
boolean logXAxis, logYAxis; // whether to really use log axis (never for small relative range)
//for passing on what should be kept when 'live' plotting (PlotMaker), but note that 'COPY_EXTRA_OBJECTS' is also on for live plotting:
int templateFlags = COPY_SIZE | COPY_LABELS | COPY_AXIS_STYLE | COPY_CONTENTS_STYLE | COPY_LEGEND;
private int dsize = PlotWindow.getDefaultFontSize();
Font defaultFont = FontUtil.getFont("Arial",Font.PLAIN,dsize); //default font for labels, axis, etc.
Font currentFont = defaultFont; // font as changed by setFont or setFontSize, must never be null
private double xScale, yScale; // pixels per data unit
private int xBasePxl, yBasePxl; // pixel coordinates corresponding to 0
private int maxIntervals = 12; // maximum number of intervals between ticks or grid lines
private int tickLength = 7; // length of major ticks
private int minorTickLength = 3; // length of minor ticks
private Color gridColor = new Color(0xc0c0c0); // light gray
private ImageProcessor ip;
private ImagePlus imp; // if we have an ImagePlus, updateAndDraw on changes
private String title;
private boolean invertedLut; // grayscale plots only, set in Edit>Options>Appearance
private boolean plotDrawn;
PlotMaker plotMaker; // for PlotMaker interface, handled by PlotWindow
private Color currentColor; // for next objects added
private Color currentColor2; // 2nd color for next object added (e.g. line for CONNECTED_CIRCLES)
float currentLineWidth;
private int currentJustification = LEFT;
private boolean ignoreForce2Grid; // after explicit setting of range (limits), ignore 'FORCE2GRID' flags
//private boolean snapToMinorGrid; // snap to grid when zooming to selection
private static double SEPARATED_BAR_WIDTH=0.5; // for plots with separate bars (e.g. categories), fraction of space, 0.1-1.0
double[] steps; // x & y interval between numbers, major ticks & grid lines, remembered for redrawing the grid
private int objectToReplace = -1; // index in allPlotObjects, for replace
//private Point2D.Double textLoc; // remembers position of previous addLabel call (replaces text if at the same position)
//private int textIndex; // remembers index of previous addLabel call (for replacing if at the same position)
/** Constructs a new Plot with the default options.
* Use add(shape,xvalues,yvalues) to add curves.
* @param title the window title
* @param xLabel the x-axis label; see setXYLabels for seting categories on an axis via the label
* @param yLabel the y-axis label; see setXYLabels for seting categories on an axis via the label
* @see #add(String,double[],double[])
* @see #add(String,double[])
*/
public Plot(String title, String xLabel, String yLabel) {
this(title, xLabel, yLabel, (float[])null, (float[])null, getDefaultFlags());
}
/** Obsolete, replaced by "new Plot(title,xLabel,yLabel); add(shape,x,y);".
* @deprecated
*/
public Plot(String title, String xLabel, String yLabel, float[] x, float[] y) {
this(title, xLabel, yLabel, x, y, getDefaultFlags());
}
/** Obsolete, replaced by "new Plot(title,xLabel,yLabel); add(shape,x,y);".
* @deprecated
*/
public Plot(String title, String xLabel, String yLabel, double[] x, double[] y) {
this(title, xLabel, yLabel, x!=null?Tools.toFloat(x):null, y!=null?Tools.toFloat(y):null, getDefaultFlags());
}
/** This version of the constructor has a 'flags' argument for
controlling whether ticks, grid, etc. are present and whether
the axes are logarithmic */
public Plot(String title, String xLabel, String yLabel, int flags) {
this(title, xLabel, yLabel, (float[])null, (float[])null, flags);
}
/** Obsolete, replaced by "new Plot(title,xLabel,yLabel,flags); add(shape,x,y);".
* @deprecated
*/
public Plot(String title, String xLabel, String yLabel, float[] xValues, float[] yValues, int flags) {
this.title = title;
pp.axisFlags = flags;
setXYLabels(xLabel, yLabel);
if (yValues != null && yValues.length>0) {
addPoints(xValues, yValues, /*yErrorBars=*/null, LINE, /*label=*/null);
allPlotObjects.get(0).flags = PlotObject.CONSTRUCTOR_DATA;
}
}
/** Obsolete, replaced by "new Plot(title,xLabel,yLabel,flags); add(shape,x,y);".
* @deprecated
*/
public Plot(String title, String xLabel, String yLabel, double[] x, double[] y, int flags) {
this(title, xLabel, yLabel, x!=null?Tools.toFloat(x):null, y!=null?Tools.toFloat(y):null, flags);
}
/** Constructs a new plot from an InputStream and closes the stream. If the ImagePlus is
* non-null, its title and ImageProcessor are used, but the image displayed is not modified.
*/
public Plot(ImagePlus imp, InputStream is) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(is);
pp = (PlotProperties)in.readObject();
allPlotObjects = (Vector<PlotObject>)in.readObject();
in.close();
if (pp.xLabel.type==8) {
pp.xLabel.updateType(); //convert old (pre-1.52i) type codes for the PlotObjects
pp.yLabel.updateType();
pp.frame.updateType();
if (pp.legend != null) pp.legend.updateType();
for (PlotObject plotObject : allPlotObjects)
plotObject.updateType();
}
defaultMinMax = pp.rangeMinMax;
currentFont = nonNullFont(pp.frame.getFont(), currentFont); // best guess in case we want to add a legend
getProcessor(); //prepares scale, calibration etc, but does not plot it yet
this.title = imp != null ? imp.getTitle() : "Untitled Plot";
if (imp != null) {
this.imp = imp;
ip = imp.getProcessor();
imp.setIgnoreGlobalCalibration(true);
adjustCalibration(imp.getCalibration());
imp.setProperty(PROPERTY_KEY, this);
}
}
/** Obsolete, replaced by "new Plot(title,xLabel,yLabel); add(shape,x,y);".
* @deprecated
*/
public Plot(String dummy, String title, String xLabel, String yLabel, float[] x, float[] y) {
this(title, xLabel, yLabel, x, y, getDefaultFlags());
}
/** Writes this plot into an OutputStream containing (1) the serialized PlotProperties and
* (2) the serialized Vector of all 'added' PlotObjects. The stream is NOT closed.
* The plot should have been drawn already.
*/
// Conversion to Streams can be also used to clone plots (not a shallow clone), but this is rather slow.
// Sample code:
// try {
// final PipedOutputStream pos = new PipedOutputStream();
// final PipedInputStream pis = new PipedInputStream(pos);
// new Thread(new Runnable() {
// final public void run() {
// try {
// Plot p = new Plot(null, pis);
// pis.close();
// pos.close();
// p.show();
// } catch(Exception e) {IJ.handleException(e);};
// }
// }, "threadMakingPlotFromStream").start();
// toStream(pos);
// } catch(Exception e) {IJ.handleException(e);}
void toStream(OutputStream os) throws IOException {
//prepare
for (PlotObject plotObject : pp.getAllPlotObjects()) //make sure all fonts are set properly
if (plotObject != null)
plotObject.setFont(nonNullFont(plotObject.getFont(), currentFont));
pp.rangeMinMax = currentMinMax;
//write
ObjectOutputStream out = new ObjectOutputStream(os);
out.writeObject(pp);
out.writeObject(allPlotObjects);
}
/** Writes this plot into a byte array containing (1) the serialized PlotProperties and
* (2) the serialized Vector of all 'added' PlotObjects.
* The plot should have been drawn already. Returns null on error (which should never happen). */
public byte[] toByteArray() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
toStream(bos);
bos.close();
return bos.toByteArray();
} catch (Exception e) {
IJ.handleException(e);
return null;
}
}
/** Returns the title of the image showing the plot (if any) or title of the plot */
public String getTitle() {
return imp == null ? title : imp.getTitle();
}
/** Sets the x-axis and y-axis range. Saves the new limits as default (so the 'R' field sets the limits to these).
* Updates the image if existing.
* Accepts NaN values to indicate auto-range. */
public void setLimits(double xMin, double xMax, double yMin, double yMax) {
setLimitsNoUpdate(xMin, xMax, yMin, yMax);
makeLimitsDefault();
ignoreForce2Grid = true;
if (plotDrawn)
setLimitsToDefaults(true);
}
/** Sets the x-axis and y-axis range. Accepts NaN values to indicate auto range.
* Does not update the image and leaves the default limits (for reset via the 'R' field) untouched. */
void setLimitsNoUpdate(double xMin, double xMax, double yMin, double yMax) {
boolean containsNaN = (Double.isNaN(xMin + xMax + yMin + yMax));
if (containsNaN && getNumPlotObjects(PlotObject.XY_DATA|PlotObject.ARROWS, false)==0)//can't apply auto-range without data
return;
double[] range = {xMin, xMax, yMin, yMax};
if (containsNaN) { //auto range for at least one limit
double[] extrema = getMinAndMax(true, ALL_AXES_RANGE);
boolean[] auto = new boolean[range.length];
for (int i = 0; i < range.length; i++)
if (Double.isNaN(range[i])) {
auto[i] = true;
range[i] = extrema[i];
}
for (int a = 0; a<range.length; a+=2) { //for all axes (0 for x, 2 for y): would semi-auto reverse the axis?
if (auto[a] == auto[a+1]) continue; //ignore if not semi-auto
boolean currentAxisReverse = defaultMinMax[a+1] < defaultMinMax[a];
if ((!currentAxisReverse && range[a+1] <= range[a]) || (currentAxisReverse && range[a] <= range[a+1])) {
auto[a] = true; //semi-auto to full-auto
auto[a+1] = true;
range[a] = extrema[a];
range[a+1] = extrema[a+1];
}
}
for (int i = 0; i < range.length; i++)
if (!auto[i]) // don't modify for limits that were set manually
enlargeRange[i] = 0;
enlargeRange(range); // for automatic limits, avoid points exactly at the border
}
System.arraycopy(range, 0, currentMinMax, 0, Math.min(range.length, currentMinMax.length));
ignoreForce2Grid = true;
}
/** Takes over the current limits as the default ones (i.e., the limits set by clicking at the 'R' field in the bottom-left corner) */
void makeLimitsDefault() {
System.arraycopy(currentMinMax, 0, defaultMinMax, 0, Math.min(currentMinMax.length, defaultMinMax.length));
}
/** Returns the current limits as an array xMin, xMax, yMin, yMax.
* (note that ImageJ versions before to 1.52i have returned incorrect values in case of log axes)
* Note that future versions might return a longer array (e.g. for y2 axis limits) */
public double[] getLimits() {
return currentMinMax.clone();//new double[] {xMin, xMax, yMin, yMax};
}
/** Sets the current limits from an array xMin, xMax, yMin, yMax
* The array may be also longer or shorter, but should not contain NaN values.
* This method should be used after the plot has been displayed.
* Does not update the plot; use updateImage() thereafter.
* Does not save the previous limits, i.e., leaves the default limits (for reset via the 'R' field) untouched.
*/
public void setLimits(double[] limits) {
System.arraycopy(limits, 0, currentMinMax, 0, Math.min(limits.length, defaultMinMax.length));
}
/** Sets options for the plot. Multiple options may be separated by whitespace or commas.
* Note that whitespace surrounding the '=' characters is not allowed.
* Currently recognized options are:
* "addhspace=10 addvspace=5" Increases the left&right or top&bottom margins by the given number of pixels.
* "xinterval=30 yinterval=90" Sets interval between numbers, major ticks & grid lines
* (default intervals are used if the custom intervals would be too dense or too sparse)
* "xdecimals=2 ydecimals=-1" Sets the minimum number of decimals; use negative numbers for scientific notation.
* "msymbol=' \\u00d7 '" Sets multiplication symbol for scientific notation, here a cross with spaces.
* */
public void setOptions(String options) {
pp.frame.options = options.toLowerCase();
}
/** Sets the canvas size in (unscaled) pixels and sets the scale to 1.0.
* If the scale remains 1.0, this will be the size of the resulting ImageProcessor.
* When not called, the canvas size is adjusted for the plot size specified
* by setFrameSize() or setWindowSize(), or otherwise in Edit>Options>Plots.
* @see #setFrameSize(int,int)
* @see #setWindowSize(int,int)
*/
public void setSize(int width, int height) {
if (ip != null && width == ip.getWidth() && height == ip.getHeight())
return;
Dimension minSize = getMinimumSize();
pp.width = Math.max(width, minSize.width);
pp.height = Math.max(height, minSize.height);
scale = 1.0f;
ip = null;
if (plotDrawn) updateImage();
}
/** The size of the plot including borders with axis labels etc., in pixels */
public Dimension getSize() {
if (ip == null)
getBlankProcessor();
return new Dimension(ip.getWidth(), ip.getHeight());
}
/** Sets the plot frame size in (unscaled) pixels. This size does not include the
* borders with the axis labels. Also sets the scale to 1.0.
* This frame size in pixels divided by the data range defines the image scale.
* This method does not check for the minimum size MIN_FRAMEWIDTH, MIN_FRAMEHEIGHT.
* Note that the black frame will have an outer size that is one pixel larger
* (when plotted with a linewidth of one pixel).
* @see #setWindowSize(int,int)
*/
public void setFrameSize(int width, int height) {
if (pp.width <= 0) { //plot not drawn yet? Just remember as preferred size
preferredPlotWidth = width;
preferredPlotHeight = height;
scale = 1.0f;
} else {
makeMarginValues();
width += leftMargin+rightMargin;
height += topMargin+bottomMargin;
setSize(width, height);
}
}
/** Sets the plot window size in pixels.
* @see #setFrameSize(int,int)
*/
public void setWindowSize(int width, int height) {
scale = 1.0f;
makeMarginValues();
int titleBarHeight = 22;
int infoHeight = 11;
double scale = Prefs.getGuiScale();
if (scale>1.0)
infoHeight = (int)(infoHeight*scale);
int buttonPanelHeight = 45;
if (pp.width <= 0) { //plot not drawn yet?
int extraWidth = leftMargin+rightMargin+ImageWindow.HGAP*2;
int extraHeight = topMargin+bottomMargin+titleBarHeight+infoHeight+buttonPanelHeight;
if (extraWidth<width)
width -= extraWidth;
if (extraHeight<height)
height -= extraHeight;
preferredPlotWidth = width;
preferredPlotHeight = height;
} else {
int extraWidth = ImageWindow.HGAP*2;
int extraHeight = titleBarHeight+infoHeight+buttonPanelHeight;
if (extraWidth<width)
width -= extraWidth;
if (extraHeight<height)
height -= extraHeight;
setSize(width, height);
}
}
/** The minimum plot size including borders, in pixels (at scale=1) */
public Dimension getMinimumSize() {
return new Dimension(MIN_FRAMEWIDTH + leftMargin + rightMargin,
MIN_FRAMEHEIGHT + topMargin + bottomMargin);
}
/** Adjusts the format with another plot as a template, using the current
* (usually default) templateFlags of this plot.
* <code>plot</code> may be null; then the call has no effect. */
public void useTemplate(Plot plot) {
useTemplate(plot, templateFlags);
}
/** Adjusts the format (style) with another plot as a template. Flags determine what to
* copy from the template; these can be X_RANGE, Y_RANGE, COPY_SIZE, COPY_LABELS, COPY_AXIS_STYLE,
* COPY_CONTENTS_STYLE (hidden items are ignored), and COPY_LEGEND.
* <code>plot</code> may be null; then the call has no effect. */
public void useTemplate(Plot plot, int templateFlags) {
if (plot == null) return;
this.defaultFont = plot.defaultFont;
this.currentFont = plot.currentFont;
this.currentLineWidth = plot.currentLineWidth;
this.currentColor = plot.currentColor;
if ((templateFlags & COPY_AXIS_STYLE) != 0) {
this.pp.axisFlags = plot.pp.axisFlags;
this.pp.frame = plot.pp.frame.deepClone();
}
if ((templateFlags & COPY_LABELS) != 0) {
this.pp.xLabel.label = plot.pp.xLabel.label;
this.pp.yLabel.label = plot.pp.yLabel.label;
this.pp.xLabel.setFont(plot.pp.xLabel.getFont());
this.pp.yLabel.setFont(plot.pp.yLabel.getFont());
}
for (int i=0; i<currentMinMax.length; i++)
if ((templateFlags>>(i/2)&0x1) != 0) {
currentMinMax[i] = plot.currentMinMax[i];
if (!plotDrawn) defaultMinMax[i] = plot.currentMinMax[i];
}
if ((templateFlags & COPY_LEGEND) != 0 && plot.pp.legend != null)
this.pp.legend = plot.pp.legend.deepClone();
if ((templateFlags & (COPY_LEGEND | COPY_CONTENTS_STYLE)) != 0) {
int plotPObjectIndex = 0; //points to PlotObjects of the templatePlot
int plotPObjectsSize = plot.allPlotObjects.size();
for (PlotObject plotObject : allPlotObjects) {
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN)) {
while(plotPObjectIndex<plotPObjectsSize &&
(plot.allPlotObjects.get(plotPObjectIndex).type != PlotObject.XY_DATA ||
plot.allPlotObjects.get(plotPObjectIndex).hasFlag(PlotObject.HIDDEN)))
plotPObjectIndex++; //skip everything that is invisible or has no label
if (plotPObjectIndex>=plotPObjectsSize) break;
if ((templateFlags & COPY_LEGEND) != 0)
plotObject.label = plot.allPlotObjects.get(plotPObjectIndex).label;
if ((templateFlags & COPY_CONTENTS_STYLE) != 0)
setPlotObjectStyle(plotObject, getPlotObjectStyle(plot.allPlotObjects.get(plotPObjectIndex)));
plotPObjectIndex++;
}
}
}
if ((templateFlags & COPY_SIZE) != 0)
setSize(plot.pp.width, plot.pp.height);
if ((templateFlags & COPY_EXTRA_OBJECTS) != 0)
for (int p = allPlotObjects.size(); p < plot.allPlotObjects.size(); p++)
allPlotObjects.add(plot.allPlotObjects.get(p));
this.templateFlags = templateFlags;
}
/** Sets the scale. Everything, including labels, line thicknesses, etc will be scaled by this factor.
* Also multiplies the plot size by this value. Used for 'Create high-resolution plot'.
* Should be called before creating the plot.
* Note that plots with a scale different from 1.0 must not be shown in a PlotWindow, but only as
* simple image in a normal ImageWindow. */
public void setScale(float scale) {
this.scale = scale;
if (scale > 20f) scale = 20f;
if (scale < 0.7f) scale = 0.7f;
pp.width = sc(pp.width);
pp.height = sc(pp.height);
plotDrawn = false;
}
/** Sets the labels of the x and y axes. 'xLabel', 'yLabel' may be null.
* If a label has the form {txt1,txt2,txt3}, the corresponding axis will be labeled
* not by numbers but rather with the texts "txt1", "txt2" ... instead of 0, 1, ...
* In this special case, there will be no label for the axis on the plot.
* Call update() thereafter to make the change visible (if it is shown already). */
public void setXYLabels(String xLabel, String yLabel) {
pp.xLabel.label = xLabel!=null ? xLabel : "";
pp.yLabel.label = yLabel!=null ? yLabel : "";
}
/** Sets the maximum number of intervals in a plot.
* Call update() thereafter to make the change visible (if the image is shown already). */
public void setMaxIntervals(int intervals) {
maxIntervals = intervals;
}
/** Sets the length of the major tick in pixels.
* Call update() thereafter to make the change visible (if the image is shown already). */
public void setTickLength(int tickLength) {
this.tickLength = tickLength;
}
/** Sets the length of the minor tick in pixels. */
public void setMinorTickLength(int minorTickLength) {
this.minorTickLength = minorTickLength;
}
/** Sets the flags that control the axes format.
* Does not modify the flags for logarithmic axes on/off and the FORCE2GRID flags.
* Call update() thereafter to make the change visible (if it is shown already). */
public void setFormatFlags(int flags) {
int unchangedFlags = X_LOG_NUMBERS | Y_LOG_NUMBERS | X_FORCE2GRID | Y_FORCE2GRID;
flags = flags & (~unchangedFlags); //remove flags that should not be affected
pp.axisFlags = (pp.axisFlags & unchangedFlags) | flags;
}
/** Returns the flags that control the axes */
public int getFlags() {
return pp.axisFlags;
}
/** Sets the X Axis format to Log or Linear.
* Call update() thereafter to make the change visible (if it is shown already). */
public void setAxisXLog(boolean axisXLog) {
pp.axisFlags = axisXLog ? pp.axisFlags | X_LOG_NUMBERS : pp.axisFlags & (~X_LOG_NUMBERS);
}
/** Sets the Y Axis format to Log or Linear.
* Call update() thereafter to make the change visible (if it is shown already). */
public void setAxisYLog(boolean axisYLog) {
pp.axisFlags = axisYLog ? pp.axisFlags | Y_LOG_NUMBERS : pp.axisFlags & (~Y_LOG_NUMBERS);
}
/** Sets whether to show major ticks at the x axis.
* Call update() thereafter to make the change visible (if the image is shown already). */
public void setXTicks(boolean xTicks) {
pp.axisFlags = xTicks ? pp.axisFlags | X_TICKS : pp.axisFlags & (~X_TICKS);
}
/** Sets whether to show major ticks at the y axis.
* Call update() thereafter to make the change visible (if the image is shown already). */
public void setYTicks(boolean yTicks) {
pp.axisFlags = yTicks ? pp.axisFlags | Y_TICKS : pp.axisFlags & (~Y_TICKS);
}
/** Sets whether to show minor ticks on the x axis (if linear). Also sets major ticks if true and no grid is set.
* Call update() thereafter to make the change visible (if the image is shown already). */
public void setXMinorTicks(boolean xMinorTicks) {
pp.axisFlags = xMinorTicks ? pp.axisFlags | X_MINOR_TICKS : pp.axisFlags & (~X_MINOR_TICKS);
if (xMinorTicks && !hasFlag(X_GRID))
pp.axisFlags |= X_TICKS;
}
/** Sets whether to show minor ticks on the y axis (if linear). Also sets major ticks if true and no grid is set.
* Call update() thereafter to make the change visible (if the image is shown already). */
public void setYMinorTicks(boolean yMinorTicks) {
pp.axisFlags = yMinorTicks ? pp.axisFlags | Y_MINOR_TICKS : pp.axisFlags & (~Y_MINOR_TICKS);
if (yMinorTicks && !hasFlag(Y_GRID))
pp.axisFlags |= Y_TICKS;
}
/** Sets the properties of the axes. Call update() thereafter to make the change visible
* (if the image is shown already). */
public void setAxes(boolean xLog, boolean yLog, boolean xTicks, boolean yTicks, boolean xMinorTicks, boolean yMinorTicks,
int tickLenght, int minorTickLenght) {
setAxisXLog (xLog);
setAxisYLog (yLog);
setXMinorTicks (xMinorTicks);
setYMinorTicks (yMinorTicks);
setXTicks (xTicks);
setYTicks (yTicks);
setTickLength (tickLenght);
setMinorTickLength(minorTickLenght);
}
/** Sets log scale in x. Call update() thereafter to make the change visible
* (if the image is shown already). */
public void setLogScaleX() {
setAxisXLog(true);
}
public void setLogScaleY() {
setAxisYLog(true);
}
/** The default flags, taking PlotWindow.noGridLines, PlotWindow.noTicks into account */
public static int getDefaultFlags() {
int defaultFlags = 0;
if (!PlotWindow.noGridLines) //note that log ticks are also needed because the range may span less than a decade, then no grid is visible
defaultFlags |= X_GRID | Y_GRID | X_NUMBERS | Y_NUMBERS | X_LOG_TICKS | Y_LOG_TICKS;
if (!PlotWindow.noTicks)
defaultFlags |= X_TICKS | Y_TICKS | X_MINOR_TICKS | Y_MINOR_TICKS | X_NUMBERS | Y_NUMBERS | X_LOG_TICKS | Y_LOG_TICKS;
return defaultFlags;
}
/** Adds a curve or set of points to this plot, where 'type' is
* "line", "connected circle", "filled", "bar", "separated bar", "circle", "box", "triangle", "diamond", "cross",
* "x" or "dot". Run <i>Help>Examples>JavaScript>Graph Types</i> to see examples.
* If 'type' is in the form "code: <macroCode>", the macro given is executed to draw the symbol;
* macro variables 'x' and 'y' are the pixel coordinates of the point, 'xval' and 'yval' are the plot data
* and 'i' is the index of the data point (starting with 0 for the first point in the array).
* The drawing including line thickness, font size, etc. be scaled by scale factor 's' (to make high-resolution plots work).
* Example: "code: setFont('sanserif',12*s,'bold anti');drawString(d2s(yval,1),x-14*s,y-4*s);"
* writes the y value for each point above the point.
*/
public void add(String type, double[] xvalues, double[] yvalues) {
int iShape = toShape(type);
addPoints(Tools.toFloat(xvalues), Tools.toFloat(yvalues), null, iShape, iShape==CUSTOM?type.substring(5, type.length()):null);
}
/** Replaces the specified plot object (curve or set of points).
* Equivalent to add() if there are no plot objects. */
public void replace(int index, String type, double[] xvalues, double[] yvalues) {
if (index>=0 && index<allPlotObjects.size()) {
objectToReplace = allPlotObjects.size()>0?index:-1;
add(type, xvalues, yvalues);
}
}
/** Adds a curve, set of points or error bars to this plot, where 'type' is
* "line", "connected circle", "filled", "bar", "separated bar", "circle", "box",
* "triangle", "diamond", "cross", "x", "dot", "error bars" or "xerror bars".
*/
public void add(String type, double[] yvalues) {
int iShape = toShape(type);
if (iShape==-1)
addErrorBars(yvalues);
else if (iShape==-2)
addHorizontalErrorBars(yvalues);
else
addPoints(null, Tools.toFloat(yvalues), null, iShape, iShape==CUSTOM?type.substring(5, type.length()):null);
}
/** Adds a set of points to the plot or adds a curve if shape is set to LINE.
* @param xValues the x coordinates, or null. If null, integers starting at 0 will be used for x.
* @param yValues the y coordinates (must not be null)
* @param yErrorBars error bars in y, may be null
* @param shape CIRCLE, X, BOX, TRIANGLE, CROSS, DIAMOND, DOT, LINE, CONNECTED_CIRCLES
* @param label Label for this curve or set of points, used for a legend and for listing the plots.
* For shape type CUSTOM, the 'label' String should contain the macro code as in "Custom Plot Symbols"
* example macro and in add(String, double[], double[]) (without the 'code:')
*/
public void addPoints(float[] xValues, float[] yValues, float[] yErrorBars, int shape, String label) {
if (xValues==null || xValues.length==0) {
xValues = new float[yValues.length];
for (int i=0; i<yValues.length; i++)
xValues[i] = i;
}
if (objectToReplace>=0)
allPlotObjects.set(objectToReplace, new PlotObject(xValues, yValues, yErrorBars, shape, currentLineWidth, currentColor, currentColor2, label));
else
allPlotObjects.add(new PlotObject(xValues, yValues, yErrorBars, shape, currentLineWidth, currentColor, currentColor2, label));
objectToReplace = -1;
if (plotDrawn) updateImage();
}
/** Adds a set of points to the plot or adds a curve if shape is set to LINE.
* @param x the x coordinates
* @param y the y coordinates
* @param shape CIRCLE, X, BOX, TRIANGLE, CROSS, DIAMOND, DOT, LINE, CONNECTED_CIRCLES
*/
public void addPoints(float[] x, float[] y, int shape) {
addPoints(x, y, null, shape, null);
}
/** Adds a set of points to the plot using double arrays. */
public void addPoints(double[] x, double[] y, int shape) {
addPoints(Tools.toFloat(x), Tools.toFloat(y), shape);
}
/** Returns the number for a given plot symbol shape, -1 for xError and -2 for yError (all case-insensitive) */
public static int toShape(String str) {
str = str.toLowerCase(Locale.US);
int shape = Plot.CIRCLE;
if (str.contains("curve") || str.contains("line"))
shape = Plot.LINE;
else if (str.contains("connected"))
shape = Plot.CONNECTED_CIRCLES;
else if (str.contains("filled"))
shape = Plot.FILLED;
else if (str.contains("circle"))
shape = Plot.CIRCLE;
else if (str.contains("box"))
shape = Plot.BOX;
else if (str.contains("triangle"))
shape = Plot.TRIANGLE;
else if (str.contains("cross") || str.contains("+"))
shape = Plot.CROSS;
else if (str.contains("diamond"))
shape = Plot.DIAMOND;
else if (str.contains("dot"))
shape = Plot.DOT;
else if (str.contains("xerror"))
shape = -2;
else if (str.contains("error"))
shape = -1;
else if (str.contains("x"))
shape = Plot.X;
else if (str.contains("separate"))
shape = Plot.SEPARATED_BAR;
else if (str.contains("bar"))
shape = Plot.BAR;
if (str.startsWith("code:"))
shape = CUSTOM;
return shape;
}
/** Adds a set of points to the plot using double ArrayLists.
* Must be called before the plot is displayed. */
public void addPoints(ArrayList x, ArrayList y, int shape) {
addPoints(getDoubleFromArrayList(x), getDoubleFromArrayList(y), shape);
}
/** Adds a set of points to the plot or adds a curve if shape is set to LINE.
* @param x the x-coodinates
* @param y the y-coodinates
* @param errorBars half-lengths of the vertical error bars, may be null
* @param shape CIRCLE, X, BOX, TRIANGLE, CROSS, DIAMOND, DOT or LINE
*/
public void addPoints(double[] x, double[] y, double[] errorBars, int shape) {
addPoints(Tools.toFloat(x), Tools.toFloat(y), Tools.toFloat(errorBars), shape, null);
}
/** Adds a set of points to the plot using double ArrayLists.
* Must be called before the plot is displayed. */
public void addPoints(ArrayList x, ArrayList y, ArrayList errorBars, int shape) {
addPoints(getDoubleFromArrayList(x), getDoubleFromArrayList(y), getDoubleFromArrayList(errorBars), shape);
}
public double[] getDoubleFromArrayList(ArrayList list) {
if (list == null) return null;
double[] targ = new double[list.size()];
for (int i = 0; i < list.size(); i++)
targ[i] = ((Double) list.get(i)).doubleValue();
return targ;
}
/** Adds a set of points that will be drawn as ARROWs.
* @param x1 the x-coodinates of the beginning of the arrow
* @param y1 the y-coodinates of the beginning of the arrow
* @param x2 the x-coodinates of the end of the arrow
* @param y2 the y-coodinates of the end of the arrow
*/
public void drawVectors(double[] x1, double[] y1, double[] x2, double[] y2) {
allPlotObjects.add(new PlotObject(Tools.toFloat(x1), Tools.toFloat(y1),
Tools.toFloat(x2), Tools.toFloat(y2), currentLineWidth, currentColor));
}
/**
* Adds a set of 'shapes' such as boxes and whiskers
*
* @param shapeType e.g. "boxes width=20"
* @param floatCoords eg[6][3] holding 1 Xval + 5 Yvals for 3 boxes
*/
public void drawShapes(String shapeType, ArrayList floatCoords) {
allPlotObjects.add(new PlotObject(shapeType, floatCoords, currentLineWidth, currentColor, currentColor2));
}
public static double calculateDistance(int x1, int y1, int x2, int y2) {
return java.lang.Math.sqrt((x2 - x1)*(double)(x2 - x1) + (y2 - y1)*(double)(y2 - y1));
}
/** Adds a set of vectors to the plot using double ArrayLists.
* Does not support logarithmic axes.
* Must be called before the plot is displayed. */
public void drawVectors(ArrayList x1, ArrayList y1, ArrayList x2, ArrayList y2) {
drawVectors(getDoubleFromArrayList(x1), getDoubleFromArrayList(y1), getDoubleFromArrayList(x2), getDoubleFromArrayList(y2));
}
/** Adds vertical error bars to the last data passed to the plot (via the constructor or addPoints). */
public void addErrorBars(float[] errorBars) {
PlotObject mainObject = getLastCurveObject();
if (mainObject != null)
mainObject.yEValues = errorBars;
else throw new RuntimeException("Plot can't add y error bars without data");
}
/** Adds vertical error bars to the last data passed to the plot (via the constructor or addPoints). */
public void addErrorBars(double[] errorBars) {
addErrorBars(Tools.toFloat(errorBars));
}
/** Adds horizontal error bars to the last data passed to the plot (via the constructor or addPoints). */
public void addHorizontalErrorBars(float[] xErrorBars) {
PlotObject mainObject = getLastCurveObject();
if (mainObject != null)
mainObject.xEValues = xErrorBars;
else throw new RuntimeException("Plot can't add x error bars without data");
}
/** Adds horizontal error bars to the last data passed to the plot (via the constructor or addPoints). */
public void addHorizontalErrorBars(double[] xErrorBars) {
addHorizontalErrorBars(Tools.toFloat(xErrorBars));
}
/** Draws text at the specified location, where (0,0)
* is the upper left corner of the the plot frame and (1,1) is
* the lower right corner. Uses the justification specified by setJustification().
* When called with the same position as the previous addLabel call, the text of that previous call is replaced */
public void addLabel(double x, double y, String label) {
for (int i=allPlotObjects.size()-1; i>=0; i--) {
PlotObject plotObject = allPlotObjects.get(i);
if (plotObject.type == PlotObject.NORMALIZED_LABEL) { //result of previous addLabel
if (plotObject.x == x && plotObject.y == y) {
allPlotObjects.set(i, new PlotObject(label, x, y, currentJustification,
currentFont, currentColor, PlotObject.NORMALIZED_LABEL));
return;
}
break;
}
}
allPlotObjects.add(new PlotObject(label, x, y, currentJustification, currentFont, currentColor, PlotObject.NORMALIZED_LABEL));
}
/* Draws text at the specified location, using the coordinate system defined
* by setLimits() and the justification specified by setJustification(). */
public void addText(String label, double x, double y) {
allPlotObjects.add(new PlotObject(label, x, y, currentJustification, currentFont, currentColor, PlotObject.LABEL));
}
/** Adds an automatically positioned legend, where 'labels' can be a tab-delimited or
newline-delimited list of curve or point labels in the sequence these data were added.
Hidden data sets are ignored.
If 'labels' is null or empty, the labels of the data set previously (if any) are used.
To modify the legend's style, call 'setFont' and 'setLineWidth' before 'addLegend'. */
public void addLegend(String labels) {
addLegend(labels, "auto");
}
/** Adds a legend at the position given in 'options', where 'labels' can be tab-delimited or
* newline-delimited list of curve or point labels in the sequence these data were added.
* Hidden data sets are ignored; no labels (and no delimiters) should be provided for these.
* Apart from top to bottom and bottom to top (controlled by "bottom-to-top" in the options),
* the sequence may be altered by preceding numbers followed by double underscores, such as
* "1__Other Data Set\t0__First Data Set" (the number and double underscore won't be displayed).
* When this possibility is used, only items with double underscores will be shown in the legend
* (preceding double underscores without a number make the item appear in the legend without altering the sequence).
* If 'labels' is null or empty, the labels of the data set previously (if any) are used.
To modify the legend's style, call 'setFont' and 'setLineWidth' before 'addLegend'. */
public void addLegend(String labels, String options) {
int flags = 0;
if (options!=null) {
options = options.toLowerCase();
if (options.contains("top-left"))
flags |= Plot.TOP_LEFT;
else if (options.contains("top-right"))
flags |= Plot.TOP_RIGHT;
else if (options.contains("bottom-left"))
flags |= Plot.BOTTOM_LEFT;
else if (options.contains("bottom-right"))
flags |= Plot.BOTTOM_RIGHT;
else if (!options.contains("off") && !options.contains("no"))
flags |= Plot.AUTO_POSITION;
if (options.contains("bottom-to-top"))
flags |= Plot.LEGEND_BOTTOM_UP;
if (options.contains("transparent"))
flags |= Plot.LEGEND_TRANSPARENT;
}
setLegend(labels, flags);
}
/** Adds a legend. The legend will be always drawn last (on top of everything).
* To modify the legend's style, call 'setFont' and 'setLineWidth' before 'addLegend'
* @param labels labels of the points or curves in the sequence of the data were added, tab-delimited or linefeed-delimited.
* The labels of the datasets will be set to these values. If null or not given, the labels set
* previously (if any) will be used.
* Hidden data sets are ignored; no labels (and no delimiters) should be provided for these.
* Apart from top to bottom and bottom to top (controlled by the LEGEND_BOTTOM_UP flag),
* the sequence may be altered by preceding numbers followed by double underscores, such as
* "1__Other Data Set\t0__First Data Set" (the number and double underscore won't be displayed).
* When this possibility is used, only items with double underscores will be shown in the legend
* (preceding double underscores without a number make the item appear in the legend without altering the sequence).
* @param flags Bitwise or of position (AUTO_POSITION, TOP_LEFT etc.), LEGEND_TRANSPARENT, and LEGEND_BOTTOM_UP if desired.
* Updates the image (if it is shown already). */
public void setLegend(String labels, int flags) {
if (labels != null && labels.length()>0) {
String[] allLabels = labels.split("[\n\t]");
int iPart = 0;
for (PlotObject plotObject : allPlotObjects)
if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN))
if (iPart < allLabels.length) {
String label = allLabels[iPart++];