55import picocli .CommandLine .Parameters ;
66
77import java .awt .Color ;
8- import java .lang .NullPointerException ;
98import java .io .File ;
109import java .io .IOException ;
11- import java .io .OutputStream ;
12- import java .io .FileOutputStream ;
13- import java .io .FileNotFoundException ;
1410import java .util .ArrayList ;
15- import java .util .Scanner ;
1611import java .util .concurrent .Callable ;
1712import java .util .regex .Matcher ;
1813import java .util .regex .Pattern ;
1914
20- import org .jfree .chart .JFreeChart ;
21- import org .jfree .chart .ChartUtils ;
22- import org .jfree .data .xy .XYSeriesCollection ;
23- import org .jfree .data .xy .XYSeries ;
24-
25- import charts .CompositePlot ;
2615import objects .ToolDescriptions ;
27- import util . ExtensionFileFilter ;
16+ import scripts . Figure_Generation . PlotComposite ;
2817
2918/**
30- * Figure_GenerationCLI/CompositePlotCLI
19+ * Command line interface class for scripts.Figure_Generation.PlotComposite to create line plot images based on the output files of scripts.Figure_Generation.TagPileup.
20+ * @author Olivia Lang
21+ * @see scripts.Figure_Generation.PlotComposite
22+ * @see scripts.Figure_Generation.TagPileup
3123 */
3224@ Command (name = "composite-plot" , mixinStandardHelpOptions = true ,
3325 description = ToolDescriptions .composite_description ,
3830public class CompositePlotCLI implements Callable <Integer > {
3931
4032 @ Parameters (index = "0" , description = "Composite data to plot. (formatted like TagPileup composite output)" )
41- private File compositeData ;
33+ private File inputComposite ;
4234
43- @ Option (names = { "-o" ,
44- "--output" }, description = "specify output filename, please use PNG extension\n (default=Input filename with \" _compositePlot.png\" appended to the name in working directory of ScriptManager" )
35+ @ Option (names = { "-o" , "--output" }, description = "specify output filename, please use PNG extension\n (default=Input filename with \" _compositePlot.png\" appended to the name in working directory of ScriptManager" )
4536 private File output = null ;
4637 @ Option (names = { "-t" , "--title" }, description = "set title (default=<composite-file-name>)" )
4738 private String title = null ;
@@ -55,37 +46,11 @@ public class CompositePlotCLI implements Callable<Integer> {
5546 "--custom-colors" }, description = "indicate colors to use for each series. Must indicate a number of colors that matches number of dataseries\n "
5647 + "default behavior:\n " + "if one series input, use black\n "
5748 + "if two series input, use blue(sense) and red(anti)\n "
58- + "if greater than two series, cycle through a set of 20 preset colors." , arity = "1.." )
49+ + "if greater than two series, cycle through a set of 10 preset colors based on Rossi et al, 2021 (PMID:33692541) ." , arity = "1.." )
5950 private String [] colors = null ;
6051
61- XYSeriesCollection xydata = null ;
6252 private ArrayList <Color > COLORS = new ArrayList <Color >();
6353
64- // Colors copied from response on StackOverflow:
65- // https://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors
66- String [] KELLY_COLORS_HEX = { "0xFFB300" , // Vivid Yellow
67- "0x803E75" , // Strong Purple
68- "0xFF6800" , // Vivid Orange
69- "0xA6BDD7" , // Very Light Blue
70- "0xC10020" , // Vivid Red
71- "0xCEA262" , // Grayish Yellow
72- "0x817066" , // Medium Gray
73- // The following don't work well for people with defective color vision
74- "0x007D34" , // Vivid Green
75- "0xF6768E" , // Strong Purplish Pink
76- "0x00538A" , // Strong Blue
77- "0xFF7A5C" , // Strong Yellowish Pink
78- "0x53377A" , // Strong Violet
79- "0xFF8E00" , // Vivid Orange Yellow
80- "0xB32851" , // Strong Purplish Red
81- "0xF4C800" , // Vivid Greenish Yellow
82- "0x7F180D" , // Strong Reddish Brown
83- "0x93AA00" , // Vivid Yellowish Green
84- "0x593315" , // Deep Yellowish Brown
85- "0xF13A13" , // Vivid Reddish Orange
86- "0x232C16" , // Dark Olive Green
87- };
88-
8954 @ Override
9055 public Integer call () throws Exception {
9156 System .err .println (">CompositePlotCLI.call()" );
@@ -97,98 +62,52 @@ public Integer call() throws Exception {
9762 }
9863
9964 // Generate Composite Plot
100- JFreeChart chart = CompositePlot .createChart (xydata , title , COLORS , legend );
101- // Save Composite Plot
102- OutputStream OUT = new FileOutputStream (output );
103- ChartUtils .writeChartAsPNG (OUT , chart , pixelWidth , pixelHeight );
65+ PlotComposite .plotCompositeFile (inputComposite , output , true , title , COLORS , legend , pixelWidth , pixelHeight );
10466
10567 System .err .println ("Image Generated." );
10668 return (0 );
10769 }
10870
71+ /**
72+ * Validate input values and create user-readable error messages.
73+ * @return
74+ * @throws IOException
75+ */
10976 private String validateInput () throws IOException {
11077 String r = "" ;
11178
112- // check inputs exist
113- if (!compositeData .exists ()) {
114- r += "(!)Composite Data file does not exist: " + compositeData .getName () + "\n " ;
115- return (r );
116- }
117- // check input extensions
118- if (!"out" .equals (ExtensionFileFilter .getExtension (compositeData ))) {
119- r += "(!)Is this a \" .out\" file? Check extension: " + compositeData .getName () + "\n " ;
79+ // Input file check
80+ if (!inputComposite .exists ()) {
81+ r += "(!)Composite Data file does not exist: " + inputComposite .getName () + "\n " ;
82+ } else if (inputComposite .isDirectory ()) {
83+ r += "(!)Composite Data file is a directory: " + inputComposite .getName () + "\n " ;
12084 }
121- // set default output filename
122- if (output == null ) {
123- output = new File (ExtensionFileFilter .stripExtension (compositeData ) + "_compositePlot.png" );
124- // check output filename is valid
125- } else {
126- // check ext
127- try {
128- if (!"png" .equals (ExtensionFileFilter .getExtension (output ))) {
129- r += "(!)Use PNG extension for output filename. Try: " + ExtensionFileFilter .stripExtension (output )
130- + ".png\n " ;
131- }
132- } catch (NullPointerException e ) {
133- r += "(!)Output filename must have extension: use PNG extension for output filename. Try: "
134- + ExtensionFileFilter .stripExtension (output ) + ".png\n " ;
135- }
136- // check directory
85+
86+ // Output file check
87+ if (output != null ) {
13788 if (output .getParent () == null ) {
13889// System.err.println("default to current directory");
13990 } else if (!new File (output .getParent ()).exists ()) {
14091 r += "(!)Check output directory exists: " + output .getParent () + "\n " ;
14192 }
14293 }
14394
144- // check pixel ranges are valid
95+ // Pixel ranges must be valid
14596 if (pixelHeight <= 0 ) {
14697 r += "(!)Cell height must be a positive integer value! check \" -y\" flag.\" " ;
14798 }
14899 if (pixelWidth <= 0 ) {
149100 r += "(!)Cell width must be a positive integer value! check \" -x\" flag.\" " ;
150101 }
151102
152- // Parse Datafile
153- try {
154- xydata = parseData ();
155- if (xydata == null ) {
156- r += "(!)The number of y-values don't match the number of x-values" ;
157- }
158- } catch (FileNotFoundException e ) {
159- e .printStackTrace ();
160- }
161-
162- // Set Color
163- if (colors == null ) { // set defaults based on number of dataseries (n=1 -> black, n=2 -> blue,red,
164- // n=3 -> cycle through kelly colors)
165- if (xydata .getSeriesCount () == 1 ) {
166- // set color to black default unless otherwise indicated
167- COLORS .add (Color .BLACK );
168- } else if (xydata .getSeriesCount () == 2 ) {
169- // set colors to blue and red default unless otherwise indicated
170- COLORS .add (Color .BLUE );
171- COLORS .add (Color .RED );
172- } else if (xydata .getSeriesCount () > 2 ) {
173- // assign a diverse set of colors (as many as there are series)
174- for (int i = 0 ; i < xydata .getSeriesCount (); i ++) {
175- int index = i % KELLY_COLORS_HEX .length ;
176- COLORS .add (Color .decode (KELLY_COLORS_HEX [index ]));
177- }
178- }
179- } else { // set user specified values and check they match number of data series
180- // assert custom colors have been assigned and length matches number of series
181- if (colors .length != xydata .getSeriesCount ()) {
182- r += "(!)Number of colors specified(" + colors .length + ") must match number of dataseries("
183- + xydata .getSeriesCount () + ")\n " ;
184- }
103+ // Set Colors (customized)
104+ if (colors != null ) {
185105 // check color input format and decode
186106 Pattern hexColorPat = Pattern .compile ("[0-9A-Fa-f]{6}" );
187107 for (int i = 0 ; i < colors .length ; i ++) {
188108 Matcher m = hexColorPat .matcher (colors [i ]);
189109 if (!m .matches ()) {
190- r += "(!)Color(" + colors [i ]
191- + ") must be formatted as a hexidecimal String!\n \t Expected input string format: \" [0-9A-Fa-f]{6}\" \n " ;
110+ r += "(!)Color(" + colors [i ] + ") must be formatted as a hexidecimal String!\n \t Expected input string format: \" [0-9A-Fa-f]{6}\" \n " ;
192111 } else {
193112 System .err .println ("Decoding color: 0x" + colors [i ]);
194113 COLORS .add (Color .decode ("0x" + colors [i ]));
@@ -197,55 +116,4 @@ private String validateInput() throws IOException {
197116 }
198117 return (r );
199118 }
200-
201- public XYSeriesCollection parseData () throws FileNotFoundException {
202- // parse input into XYDataset obj
203- XYSeriesCollection dataset = new XYSeriesCollection ();
204- Scanner scan = new Scanner (compositeData );
205- // parse x values
206- String [] tokens = scan .nextLine ().split ("\t " );
207- if (!tokens [0 ].equals ("" )) {
208- scan .close ();
209- System .err .println ("(!) First row of input file must have an empty first column (as x-values)" );
210- return null ;
211- }
212- double [] x = new double [tokens .length - 1 ];
213- for (int i = 1 ; i < tokens .length ; i ++) {
214- x [i - 1 ] = Double .parseDouble (tokens [i ]);
215- }
216-
217- XYSeries s ;
218- // line-by-line through file
219- while (scan .hasNextLine ()) {
220- tokens = scan .nextLine ().split ("\t " );
221- // check for format consistency: number of x-values matches y-values
222- if (tokens .length - 1 != x .length ) {
223- System .err .println ("(!) Check number of x-values matches number of y-values" );
224- scan .close ();
225- return null ;
226- }
227- // skip any rows with blank labels
228- if (tokens [0 ].equals ("" )) {
229- for (int i = 1 ; i < tokens .length ; i ++) {
230- if (x [i - 1 ] != Double .parseDouble (tokens [i ])) {
231- System .err .println (x [i - 1 ]);
232- System .err .println (tokens [i ]);
233- System .err .println ("(!) Check dataseries based on same x-scale file" );
234- scan .close ();
235- return null ;
236- }
237- }
238- continue ;
239- }
240- // initialize series based on rowname column of input file
241- s = new XYSeries (tokens [0 ]);
242- // parse y-values of current row
243- for (int i = 1 ; i < tokens .length ; i ++) {
244- s .add (x [i - 1 ], Double .parseDouble (tokens [i ]));
245- }
246- dataset .addSeries (s );
247- }
248- scan .close ();
249- return (dataset );
250- }
251119}
0 commit comments