Repository: jtablesaw/tablesaw
Pull request #481 (2019-03-07 08:43:27)

Updated the shade configuration, so it creates jars that can be used for
OSGi bundles and Eclipse integration.

Thanks for contributing.

- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

Shade configurations were updated/created in each module's pom.xml files.

## Testing

I checked that the shaded jars were usable in the Eclipse integration.


benmccann: Listing every transitive dependency seems extremely brittle and annoying to maintain. If the shade plugin can't be more easily configured, perhaps [the bundle plugin](http://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html) could be used instead? I think it's more directly meant for building OSGI packages and allows you to include or exclude dependencies by scope
hallvard: I completely agree, but this is what I got working in my first attempt. I looked at the shaded jar for jsplot and it included everything, but I may have looked at an old one from before the modules were configured with provided. I'll try a bit more... I know of the bundle plugin, but I think it requires a bit more elaborate configuration, but I'll dig more into it. I really want to robust and easy to maintain setup, that doesn't interfere with the rest of the build setup for the non-OSGi case.
hallvard: The core needs a list of dependencies to exclude, since shade doesn't seem to support omitting optional dependencies. It works with provided dependencies, so I can remove the include list for html, json and html. jsplot is different, since it just depends on core the normal way. There's a suggestion here: https://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies, but I haven't tried it, yet.
benmccann: Why do you need separate jars for Eclipse? Won't you want to include all the different readers? Why not just make one uber jar that includes everything including core and the readers
hallvard: Requiring the user to install all the features (in this case, readers), including the 19Mb xlsx reader, goes against the idea of modular and extensible frameworks, that is key to Eclipse's architecture. In the Eclipse integration of tablesaw the optional readers are provided by optional plug-ins (bundles) that will only be activated if the corresponding file types are used and opened. This is done using Eclipse's extension mechanism. I know classes in general aren't loaded before they're actively used, but jar size is still an issue. Tablesaw could use a similar trick with a registry of readers and a Table.read method that takes the file type as its only argument. This will also make it possible to avoid the "reversed" optional dependency.
benmccann: Wow. I didn't realize `poi-ooxml` and dependencies are 19Mb. I would have thought it'd be much smaller

I like the idea of using a registry. I think the current API is nicer than having to pass the file extension. But I'm not sure you'd have to change the API to implement a registry.
hallvard: The poi stuff is generic for the whole set of office file formats. There are smaller libraries that support just xls, but I'm not sure they support xlsx.

The problem with an API with specific methods for each file format is the dependency from the core to the file format module. There's lots of formats out there, e.g. Smile supports several that could be of interest to tablesaw (without using Smile), and each would need another method in the DataFrameReader and another dependency from core to be "officially" supported. With a file format registry (and some way of discovering the modules providing them) this wouldn't be a problem. Yes, you can implement a registry without changing the API, but that wouldn't give you the advantage of increased modularity.
benmccann: I think we could do both. Have a registry which supports new formats. But have an easy fluent API for the most common readers which we support directly which calls the registry under the hood instead of calling the specific reader instance directly

Patch:
diff --git a/README.md b/README.md
index 9845073a..f02f39aa 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ __Tablesaw__ is Java for data science. It includes a dataframe and a visualizati
 ### Tablesaw features
 
 #### Data processing & transformation
-* Import data from RDBMS, CSV, JSON, HTML, or Fixed Width text files, whether they are local or remote (http, S3, etc.)
+* Import data from RDBMS, Excel, CSV, JSON, HTML, or Fixed Width text files, whether they are local or remote (http, S3, etc.)
 * Export data to CSV, JSON, HTML or Fixed Width files. 
 * Combine tables by appending or joining
 * Add and remove columns or rows
@@ -43,12 +43,12 @@ Add tablesaw-core and tablesaw-jsplot to your project:
 <dependency>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-core</artifactId>
-    <version>0.30.4</version>
+    <version>0.31.0</version>
 </dependency>
 <dependency>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-jsplot</artifactId>
-    <version>0.30.4</version>
+    <version>0.31.0</version>
 </dependency>
 ```
 
diff --git a/core/pom.xml b/core/pom.xml
index caa03ea7..b64d6bd1 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -115,24 +115,28 @@
             <artifactId>tablesaw-excel</artifactId>
             <version>0.31.1-SNAPSHOT</version>
             <optional>true</optional>
+            <scope>provided</scope> <!-- because the shade plugin doesn't exclude optional dependencies -->
         </dependency>
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-html</artifactId>
             <version>0.31.1-SNAPSHOT</version>
             <optional>true</optional>
+            <scope>provided</scope> <!-- because the shade plugin doesn't exclude optional dependencies -->
         </dependency>
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-json</artifactId>
             <version>0.31.1-SNAPSHOT</version>
             <optional>true</optional>
+            <scope>provided</scope> <!-- because the shade plugin doesn't exclude optional dependencies -->
         </dependency>
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-smile</artifactId>
             <version>0.31.1-SNAPSHOT</version>
             <optional>true</optional>
+            <scope>provided</scope> <!-- because the shade plugin doesn't exclude optional dependencies -->
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
index dd195132..3b02687f 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
@@ -30,6 +30,7 @@ public class CsvReadOptions extends ReadOptions {
     private final Character separator;
     private final String lineEnding;
     private final Integer maxNumberOfColumns;
+    private final Character commentPrefix;
 
     private CsvReadOptions(CsvReadOptions.Builder builder) {
 	super(builder);
@@ -38,6 +39,7 @@ public class CsvReadOptions extends ReadOptions {
         separator = builder.separator;
         lineEnding = builder.lineEnding;
         maxNumberOfColumns = builder.maxNumberOfColumns;
+        commentPrefix = builder.commentPrefix;
     }
 
     public static Builder builder(File file) {
@@ -139,13 +141,18 @@ public class CsvReadOptions extends ReadOptions {
         return maxNumberOfColumns;
     }
 
+    public Character commentPrefix() {
+        return commentPrefix;
+    }
+
     public static class Builder extends ReadOptions.Builder {
 
         private Character separator = ',';
         private String lineEnding;
         private ColumnType[] columnTypes;
         private Integer maxNumberOfColumns = 10_000;
-
+        private Character commentPrefix;
+        
         public Builder(File file) {
             super(file);
         }
@@ -244,6 +251,11 @@ public class CsvReadOptions extends ReadOptions {
             return this;
         }
 
+        public Builder commentPrefix(char commentPrefix) {
+            this.commentPrefix = commentPrefix;
+            return this;
+        }
+
         public CsvReadOptions build() {
             return new CsvReadOptions(this);
         }
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
index 6da6fa86..5063febd 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
@@ -163,6 +163,9 @@ public class CsvReader extends FileReader {
         if (options.lineEnding() != null) {
             format.setLineSeparator(options.lineEnding());
         }
+        if(options.commentPrefix() != null) {
+            format.setComment(options.commentPrefix());
+        }
         return format;
     }
 
diff --git a/data/with_comments.csv b/data/with_comments.csv
new file mode 100644
index 00000000..45330b85
--- /dev/null
+++ b/data/with_comments.csv
@@ -0,0 +1,11 @@
+#comment1
+#comment2
+Products,Sales,Market_Share
+# comment3
+a,5500,3
+# comment,with,some,rows
+#comment5
+b,12200,4
+c,60000,33
+#comment,with,rows
+#footer comment
\ No newline at end of file
diff --git a/excel/pom.xml b/excel/pom.xml
index 3066a3d9..99b93901 100644
--- a/excel/pom.xml
+++ b/excel/pom.xml
@@ -49,6 +49,25 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
       </plugin>
+      
+       <plugin>
+           <groupId>org.apache.maven.plugins</groupId>
+           <artifactId>maven-shade-plugin</artifactId>
+           <version>3.1.0</version>
+           <configuration>
+               <shadedArtifactAttached>true</shadedArtifactAttached>
+               <shadedClassifierName>shaded</shadedClassifierName>
+           </configuration>
+           <executions>
+               <execution>
+                   <phase>package</phase>
+                   <goals>
+                       <goal>shade</goal>
+                   </goals>
+               </execution>
+           </executions>
+       </plugin>
+      
     </plugins>
   </build>
 
diff --git a/html/pom.xml b/html/pom.xml
index 44fa1f0c..cecbc89e 100644
--- a/html/pom.xml
+++ b/html/pom.xml
@@ -49,6 +49,25 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
       </plugin>
+      
+      <plugin>
+         <groupId>org.apache.maven.plugins</groupId>
+         <artifactId>maven-shade-plugin</artifactId>
+         <version>3.1.0</version>
+         <configuration>
+             <shadedArtifactAttached>true</shadedArtifactAttached>
+             <shadedClassifierName>shaded</shadedClassifierName>
+         </configuration>
+         <executions>
+             <execution>
+                 <phase>package</phase>
+                 <goals>
+                     <goal>shade</goal>
+                 </goals>
+             </execution>
+         </executions>
+     </plugin>
+      
     </plugins>
   </build>
 
diff --git a/json/pom.xml b/json/pom.xml
index 9ed103cc..ac8241e9 100644
--- a/json/pom.xml
+++ b/json/pom.xml
@@ -49,6 +49,25 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
       </plugin>
+      
+      <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-shade-plugin</artifactId>
+          <version>3.1.0</version>
+          <configuration>
+              <shadedArtifactAttached>true</shadedArtifactAttached>
+              <shadedClassifierName>shaded</shadedClassifierName>
+          </configuration>
+          <executions>
+              <execution>
+                  <phase>package</phase>
+                  <goals>
+                      <goal>shade</goal>
+                  </goals>
+              </execution>
+          </executions>
+      </plugin>
+      
     </plugins>
   </build>
 
diff --git a/jsplot/pom.xml b/jsplot/pom.xml
index 32247c5a..f999474f 100644
--- a/jsplot/pom.xml
+++ b/jsplot/pom.xml
@@ -48,6 +48,32 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
             </plugin>
+            
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <shadedArtifactAttached>true</shadedArtifactAttached>
+                    <shadedClassifierName>shaded</shadedClassifierName>
+                    <artifactSet>
+                        <includes>
+                            <include>io.pebbletemplates:pebble</include>
+                            <include>com.coverity.security:coverity-escapers</include>
+                            <include>org.slf4j:slf4j-api</include>
+                        </includes>
+                    </artifactSet>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            
         </plugins>
     </build>
     <dependencies>
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/AreaPlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/AreaPlot.java
index adbab843..dfb3f120 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/AreaPlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/AreaPlot.java
@@ -1,7 +1,6 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.traces.ScatterTrace;
@@ -11,14 +10,10 @@ import java.util.List;
 
 public class AreaPlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String xCol, String yCol, String groupCol) {
-
         TableSliceGroup tables = table.splitOn(table.categoricalColumn(groupCol));
 
-        Layout layout = standardLayout(title, xCol, yCol).build();
+        Layout layout = Layout.builder(title, xCol, yCol).showLegend(true).build();
 
         ScatterTrace[] traces  = new ScatterTrace[tables.size()];
         for (int i = 0; i < tables.size(); i++) {
@@ -35,24 +30,8 @@ public class AreaPlot {
         return new Figure(layout, traces);
     }
 
-    private static Layout.LayoutBuilder standardLayout(String title, String xCol, String yCol) {
-        return Layout.builder()
-                    .title(title)
-                    .height(HEIGHT)
-                    .width(WIDTH)
-                    .showLegend(true)
-                    .xAxis(Axis.builder()
-                            .title(xCol)
-                            .build())
-                    .yAxis(Axis.builder()
-                            .title(yCol)
-                            .build());
-    }
-
     public static Figure create(String title, Table table, String xCol, String yCol) {
-
-        Layout layout = standardLayout(title, xCol, yCol).build();
-
+        Layout layout = Layout.builder(title, xCol, yCol).showLegend(true).build();
         ScatterTrace trace = ScatterTrace.builder(
                 table.numberColumn(xCol),
                 table.numberColumn(yCol))
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/BubblePlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/BubblePlot.java
index da0356ba..9de6f444 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/BubblePlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/BubblePlot.java
@@ -1,7 +1,6 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.components.Marker;
@@ -12,24 +11,10 @@ import java.util.List;
 
 public class BubblePlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String xCol, String yCol, String sizeColumn, String groupCol) {
 
         TableSliceGroup tables = table.splitOn(table.categoricalColumn(groupCol));
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .showLegend(true)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yCol)
-                        .build())
-                .build();
+        Layout layout = Layout.builder(title, xCol, yCol).showLegend(true).build();
 
         ScatterTrace[] traces  = new ScatterTrace[tables.size()];
         for (int i = 0; i < tables.size(); i++) {
@@ -52,18 +37,7 @@ public class BubblePlot {
     }
 
     public static Figure create(String title, Table table, String xCol, String yCol, String sizeColumn) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yCol)
-                        .build())
-                .build();
+        Layout layout = Layout.builder(title, xCol, yCol).build();
 
         Marker marker = Marker.builder()
                 .size(table.numberColumn(sizeColumn))
@@ -79,19 +53,7 @@ public class BubblePlot {
     }
 
     public static Figure create(String title, String xTitle, double[] xCol, String yTitle, double[] yCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xTitle)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yTitle)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xTitle, yTitle).build();
         ScatterTrace trace = ScatterTrace.builder(xCol, yCol).build();
         return new Figure(layout, trace);
     }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/CandlestickPlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/CandlestickPlot.java
index 2a66ca52..e492a084 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/CandlestickPlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/CandlestickPlot.java
@@ -1,27 +1,14 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.traces.ScatterTrace;
 
 public class CandlestickPlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String xCol, String openCol, String highCol, String lowCol, String closeCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xCol).build();
         ScatterTrace trace = ScatterTrace.builder(
                 table.dateColumn(xCol),
                 table.numberColumn(openCol),
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/Heatmap.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/Heatmap.java
index 69e3e137..e00ccb66 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/Heatmap.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/Heatmap.java
@@ -10,17 +10,11 @@ import tech.tablesaw.util.DoubleArrays;
 import java.util.List;
 
 public class Heatmap {
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
 
     private Heatmap() {}
 
     public static Figure create(String title, Table table, String categoryCol1, String categoryCol2) {
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .build();
+        Layout layout = Layout.builder(title).build();
 
         Table counts = table.xTabCounts(categoryCol1, categoryCol2);
         counts = counts.dropRows(counts.rowCount() - 1);
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram.java
index 3993701e..dbfe8715 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram.java
@@ -8,9 +8,6 @@ import tech.tablesaw.plotly.traces.HistogramTrace;
 
 public class Histogram {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, NumberColumn<?> data) {
         return create(title, data.asDoubleArray());
     }
@@ -21,14 +18,7 @@ public class Histogram {
 
     public static Figure create(String title, double[] data) {
         HistogramTrace trace = HistogramTrace.builder(data).build();
-        return new Figure(standardLayout(title), trace);
+        return new Figure(Layout.builder(title).build(), trace);
     }
 
-    private static Layout standardLayout(String title) {
-        return Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .build();
-    }
 }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram2D.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram2D.java
index 8c1d0365..bee71029 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram2D.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/Histogram2D.java
@@ -1,31 +1,17 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.traces.Histogram2DTrace;
 
 public class Histogram2D {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String xCol, String yCol) {
-
         Histogram2DTrace trace = Histogram2DTrace.builder(table.numberColumn(xCol), table.numberColumn(yCol))
                 .build();
 
-        return new Figure(standardLayout(title, xCol, yCol), trace);
+        return new Figure(Layout.builder(title, xCol, yCol).build(), trace);
     }
 
-    private static Layout standardLayout(String title, String xCol, String yCol) {
-        return Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder().title(xCol).build())
-                .yAxis(Axis.builder().title(yCol).build())
-                .build();
-    }
 }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/LinePlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/LinePlot.java
index bfe7ce24..dd34c2da 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/LinePlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/LinePlot.java
@@ -1,7 +1,6 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.traces.ScatterTrace;
@@ -11,25 +10,11 @@ import java.util.List;
 
 public class LinePlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String xCol, String yCol, String groupCol) {
 
         TableSliceGroup tables = table.splitOn(table.categoricalColumn(groupCol));
 
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .showLegend(true)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yCol)
-                        .build())
-                .build();
+        Layout layout = Layout.builder(title, xCol, yCol).showLegend(true).build();
 
         ScatterTrace[] traces  = new ScatterTrace[tables.size()];
         for (int i = 0; i < tables.size(); i++) {
@@ -46,19 +31,7 @@ public class LinePlot {
     }
 
     public static Figure create(String title, Table table, String xCol, String yCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yCol)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xCol, yCol).build();
         ScatterTrace trace = ScatterTrace.builder(
                 table.numberColumn(xCol),
                 table.numberColumn(yCol))
@@ -68,19 +41,7 @@ public class LinePlot {
     }
 
     public static Figure create(String title, String xTitle, double[] xCol, String yTitle, double[] yCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xTitle)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yTitle)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xTitle, yTitle).build();
         ScatterTrace trace = ScatterTrace.builder(xCol, yCol)
                 .mode(ScatterTrace.Mode.LINE)
                 .build();
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/OHLCPlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/OHLCPlot.java
index 9a595ebd..e367422a 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/OHLCPlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/OHLCPlot.java
@@ -1,27 +1,14 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.traces.ScatterTrace;
 
 public class OHLCPlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String xCol, String openCol, String highCol, String lowCol, String closeCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xCol).build();
         ScatterTrace trace = ScatterTrace.builder(
                 table.dateColumn(xCol),
                 table.numberColumn(openCol),
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/PiePlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/PiePlot.java
index c7582825..ed58c367 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/PiePlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/PiePlot.java
@@ -7,16 +7,9 @@ import tech.tablesaw.plotly.traces.PieTrace;
 
 public class PiePlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String groupColName, String numberColName) {
 
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .build();
+        Layout layout = Layout.builder(title).build();
 
         PieTrace trace = PieTrace.builder(
                 table.column(groupColName),
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/ScatterPlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/ScatterPlot.java
index af1f32b2..ffa5fbac 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/ScatterPlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/ScatterPlot.java
@@ -1,7 +1,6 @@
 package tech.tablesaw.plotly.api;
 
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.components.Marker;
@@ -12,26 +11,13 @@ import java.util.List;
 
 public class ScatterPlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
     private static final double OPACITY = .75;
 
     public static Figure create(String title, Table table, String xCol, String yCol, String groupCol) {
 
         TableSliceGroup tables = table.splitOn(table.categoricalColumn(groupCol));
 
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .showLegend(true)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yCol)
-                        .build())
-                .build();
+        Layout layout = Layout.builder(title, xCol, yCol).showLegend(true).build();
 
         ScatterTrace[] traces  = new ScatterTrace[tables.size()];
         Marker marker = Marker.builder().opacity(OPACITY).build();
@@ -49,19 +35,7 @@ public class ScatterPlot {
     }
 
     public static Figure create(String title, Table table, String xCol, String yCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xCol)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yCol)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xCol, yCol).build();
         ScatterTrace trace = ScatterTrace.builder(
                 table.numberColumn(xCol),
                 table.numberColumn(yCol))
@@ -70,20 +44,9 @@ public class ScatterPlot {
     }
 
     public static Figure create(String title, String xTitle, double[] xCol, String yTitle, double[] yCol) {
-
-        Layout layout = Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xTitle)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yTitle)
-                        .build())
-                .build();
-
+        Layout layout = Layout.builder(title, xTitle, yTitle).build();
         ScatterTrace trace = ScatterTrace.builder(xCol, yCol).build();
         return new Figure(layout, trace);
     }
+
 }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/api/TimeSeriesPlot.java b/jsplot/src/main/java/tech/tablesaw/plotly/api/TimeSeriesPlot.java
index c38cd53f..fc9fb889 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/TimeSeriesPlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/TimeSeriesPlot.java
@@ -3,7 +3,6 @@ package tech.tablesaw.plotly.api;
 import tech.tablesaw.api.DateColumn;
 import tech.tablesaw.api.NumberColumn;
 import tech.tablesaw.api.Table;
-import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.traces.ScatterTrace;
@@ -13,14 +12,11 @@ import java.util.List;
 
 public class TimeSeriesPlot {
 
-    private static final int HEIGHT = 600;
-    private static final int WIDTH = 800;
-
     public static Figure create(String title, Table table, String dateColX, String yCol, String groupCol) {
 
         TableSliceGroup tables = table.splitOn(table.categoricalColumn(groupCol));
 
-        Layout layout = standardLayout(title, dateColX, yCol);
+        Layout layout = Layout.builder(title, dateColX, yCol).build();
 
         ScatterTrace[] traces  = new ScatterTrace[tables.size()];
         for (int i = 0; i < tables.size(); i++) {
@@ -38,9 +34,7 @@ public class TimeSeriesPlot {
     }
 
     public static Figure create(String title, Table table, String dateColXName, String yColName) {
-
-        Layout layout = standardLayout(title, dateColXName, yColName);
-
+        Layout layout = Layout.builder(title, dateColXName, yColName).build();
         ScatterTrace trace = ScatterTrace.builder(
                 table.column(dateColXName),
                 table.numberColumn(yColName))
@@ -50,26 +44,11 @@ public class TimeSeriesPlot {
     }
 
     public static Figure create(String title, String xTitle, DateColumn xCol, String yTitle, NumberColumn<?> yCol) {
-
-        Layout layout = standardLayout(title, xTitle, yTitle);
-
+        Layout layout = Layout.builder(title, xTitle, yTitle).build();
         ScatterTrace trace = ScatterTrace.builder(xCol, yCol)
                 .mode(ScatterTrace.Mode.LINE)
                 .build();
         return new Figure(layout, trace);
     }
 
-    private static Layout standardLayout(String title, String xTitle, String yTitle) {
-        return Layout.builder()
-                .title(title)
-                .height(HEIGHT)
-                .width(WIDTH)
-                .xAxis(Axis.builder()
-                        .title(xTitle)
-                        .build())
-                .yAxis(Axis.builder()
-                        .title(yTitle)
-                        .build())
-                .build();
-    }
 }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Layout.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Layout.java
index e40fae37..830ddf22 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Layout.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Layout.java
@@ -13,6 +13,8 @@ import java.util.Map;
 
 public class Layout {
 
+    private static final int DEFAULT_HEIGHT = 600;
+    private static final int DEFAULT_WIDTH = 800;
     private final static String DEFAULT_TITLE = "";
     private final static String DEFAULT_PAPER_BG_COLOR = "#fff";
     private final static String DEFAULT_PLOT_BG_COLOR = "#fff";
@@ -287,6 +289,27 @@ public class Layout {
         return new LayoutBuilder();
     }
 
+    public static LayoutBuilder builder(String title) {
+      return Layout.builder()
+          .title(title)
+          .height(DEFAULT_HEIGHT)
+          .width(DEFAULT_WIDTH);
+    }
+
+    public static LayoutBuilder builder(String title, String xTitle) {
+      return Layout.builder(title)
+          .xAxis(Axis.builder()
+                  .title(xTitle)
+                  .build());
+    }
+
+    public static LayoutBuilder builder(String title, String xTitle, String yTitle) {
+      return Layout.builder(title, xTitle)
+          .yAxis(Axis.builder()
+                  .title(yTitle)
+                  .build());
+    }
+
     public static class LayoutBuilder {
 
         /**
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Page.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Page.java
index e9ff98ed..e4964a6d 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Page.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Page.java
@@ -1,14 +1,14 @@
 package tech.tablesaw.plotly.components;
 
-import com.mitchellbosecke.pebble.error.PebbleException;
-import com.mitchellbosecke.pebble.template.PebbleTemplate;
-
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.mitchellbosecke.pebble.error.PebbleException;
+import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
 /**
  * Represents an entire html page that contains a figure
  */
@@ -17,9 +17,12 @@ public class Page extends Component {
     private final Figure figure;
     private final String divName;
 
+    private final String plotlyJsLocation;
+
     private Page(PageBuilder builder) {
         this.figure = builder.figure;
         this.divName = builder.divName;
+        this.plotlyJsLocation = builder.plotlyJsLocation;
     }
 
     @Override
@@ -41,6 +44,7 @@ public class Page extends Component {
         context.put("figureScript", figure.asJavascript(divName));
         context.put("targetDiv", figure.divString(divName));
         context.put("figureTitle", figure.getLayout() != null? figure.getLayout().getTitle() : null);
+        context.put("plotlyJsLocation", plotlyJsLocation);
         return context;
     }
 
@@ -53,6 +57,8 @@ public class Page extends Component {
         private final Figure figure;
         private final String divName;
 
+        private String plotlyJsLocation = null;
+
         public PageBuilder(Figure figure, String divName) {
             this.figure = figure;
             this.divName = divName;
@@ -61,5 +67,10 @@ public class Page extends Component {
         public Page build() {
             return new Page(this);
         }
+        
+        public PageBuilder plotlyJsLocation(String location) {
+            this.plotlyJsLocation = location;
+            return this;
+        }
     }
 }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/TemplateUtils.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/TemplateUtils.java
index 31116f1b..4343e4aa 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/TemplateUtils.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/TemplateUtils.java
@@ -1,16 +1,40 @@
 package tech.tablesaw.plotly.components;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
 import com.mitchellbosecke.pebble.PebbleEngine;
 import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.loader.ClasspathLoader;
+import com.mitchellbosecke.pebble.loader.DelegatingLoader;
+import com.mitchellbosecke.pebble.loader.FileLoader;
 import com.mitchellbosecke.pebble.loader.Loader;
 
 public class TemplateUtils {
 
+    private static Collection<String> templateLocations = new ArrayList<>(); 
+    
+    public static void setTemplateLocations(String... locations) {
+        templateLocations = Arrays.asList(locations);
+    }
+    
     public static PebbleEngine getNewEngine() {
         PebbleEngine engine;
         try {
             Loader<?> loader = new ClasspathLoader();
+            if (templateLocations != null && templateLocations.size() > 0) {
+                List<Loader<?>> loaders = new ArrayList<>();
+                for (String templateLocation : templateLocations) {
+                    FileLoader fileLoader = new FileLoader();
+                    fileLoader.setPrefix(templateLocation);
+                    loaders.add(fileLoader);
+                }
+                // add this one last, so it is shadowed
+                loaders.add(loader);
+                loader = new DelegatingLoader(loaders);
+            }
             engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
         } catch (PebbleException e) {
             e.printStackTrace();
@@ -18,6 +42,4 @@ public class TemplateUtils {
         }
         return engine;
     }
-
-
 }
diff --git a/jsplot/src/main/resources/page_template.html b/jsplot/src/main/resources/page_template.html
index 1f2f080b..5820c547 100644
--- a/jsplot/src/main/resources/page_template.html
+++ b/jsplot/src/main/resources/page_template.html
@@ -3,10 +3,11 @@
 <head>
     <meta charset="UTF-8">
     <title>{{figureTitle}}</title>
-<!--
-    <script src="src/main/resources/plotly-latest.min.js"></script>
--->
+{% if plotlyJsLocation is not null %}
+    <script src="{{plotlyJsLocation}}"></script>
+{% else %}
     <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
+{% endif %}
 </head>
 <body>
 {% if targetDiv is not null %}
diff --git a/smile/pom.xml b/smile/pom.xml
index 6e4109fd..d53cd8cc 100644
--- a/smile/pom.xml
+++ b/smile/pom.xml
@@ -49,6 +49,25 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
       </plugin>
+      
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>3.1.0</version>
+        <configuration>
+          <shadedArtifactAttached>true</shadedArtifactAttached>
+          <shadedClassifierName>shaded</shadedClassifierName>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
     </plugins>
   </build>
 


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
index b2bc9a78..3bb6b915 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
@@ -508,4 +508,13 @@ public class CsvReaderTest {
         assertEquals("1 rows X 10001 cols", table1.shape());
     }
 
+    @Test
+    public void testSkipLinesWithComments() throws Exception {
+        Table table1 = Table.read().csv(CsvReadOptions
+                .builder("../data/with_comments.csv")
+                .maxNumberOfColumns(3)
+                .commentPrefix('#')
+                .header(true));
+        assertEquals("3 rows X 3 cols", table1.shape());
+    }
 }
diff --git a/jsplot/src/test/java/tech/tablesaw/components/TemplateUtilsTest.java b/jsplot/src/test/java/tech/tablesaw/components/TemplateUtilsTest.java
new file mode 100644
index 00000000..a1e36c47
--- /dev/null
+++ b/jsplot/src/test/java/tech/tablesaw/components/TemplateUtilsTest.java
@@ -0,0 +1,53 @@
+package tech.tablesaw.components;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.net.URL;
+
+import org.junit.jupiter.api.Test;
+
+import tech.tablesaw.plotly.components.Figure;
+import tech.tablesaw.plotly.components.Page;
+import tech.tablesaw.plotly.components.Page.PageBuilder;
+import tech.tablesaw.plotly.components.TemplateUtils;
+import tech.tablesaw.plotly.traces.ScatterTrace;
+import tech.tablesaw.plotly.traces.Trace;
+
+public class TemplateUtilsTest {
+
+    private double[] x = {1, 2, 3, 4, 5};
+    private double[] y = {1, 4, 9, 16, 25};
+
+    private String customTemplateString = "<!-- Custom page_template.html -->";
+    
+    @Test
+    public void testDefaultTemplateLocation() {
+        TemplateUtils.setTemplateLocations();
+        String html = createPageHtml();
+        assertTrue(html.indexOf(customTemplateString) < 0);
+    }
+
+    @Test
+    public void testCustomTemplateLocation() {
+        URL url = this.getClass().getResource(this.getClass().getSimpleName() + ".class");
+        assertNotNull(url, "Couldn't locate class (as resource), where template is also found");
+        String path = url.getPath();
+        assertTrue(path.lastIndexOf('/') >= 0);
+        String folderPath = path.substring(0, path.lastIndexOf('/'));
+        File templateFile = new File(folderPath + "/page_template.html");
+        assertTrue(templateFile.exists(), templateFile + " doesn't exist");
+        TemplateUtils.setTemplateLocations(folderPath);
+        String html = createPageHtml();
+        assertTrue(html.indexOf(customTemplateString) >= 0);
+    }
+
+    private String createPageHtml() {
+        Trace trace = ScatterTrace.builder(x, y).build();
+        Figure figure = new Figure(trace);
+        Page page = new PageBuilder(figure, "plot").build();
+        String html = page.asJavascript();
+        return html;
+    }
+}
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/PageTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/PageTest.java
new file mode 100644
index 00000000..d0dd3f56
--- /dev/null
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/PageTest.java
@@ -0,0 +1,35 @@
+package tech.tablesaw.plotly;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import tech.tablesaw.plotly.components.Figure;
+import tech.tablesaw.plotly.components.Page;
+import tech.tablesaw.plotly.traces.BarTrace;
+
+public class PageTest {
+
+    private final Object[] x = {"sheep", "cows", "fish", "tree sloths"};
+    private final double[] y = {1, 4, 9, 16};
+
+    @Test
+    public void testDefaultPlotlyJsLocation() {
+        BarTrace trace = BarTrace.builder(x, y).build();
+        Page page = Page.pageBuilder(new Figure(trace), "plot")
+                .build();
+        String html = page.asJavascript();
+        assertTrue(html.indexOf("\"" + "https://cdn.plot.ly/plotly-latest.min.js" + "\"") > 0);
+    }
+    
+    @Test
+    public void testCustomPlotlyJsLocation() {
+        BarTrace trace = BarTrace.builder(x, y).build();
+        String location = this.getClass().getResource(this.getClass().getSimpleName() + ".class").toString();
+        Page page = Page.pageBuilder(new Figure(trace), "plot")
+                .plotlyJsLocation(location)
+                .build();
+        String html = page.asJavascript();
+        assertTrue(html.indexOf("\"" + location + "\"") > 0);
+    }
+}
diff --git a/jsplot/src/test/resources/tech/tablesaw/components/page_template.html b/jsplot/src/test/resources/tech/tablesaw/components/page_template.html
new file mode 100644
index 00000000..022b5850
--- /dev/null
+++ b/jsplot/src/test/resources/tech/tablesaw/components/page_template.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<!-- Custom page_template.html -->
+<!-- The text above is used by TemplateUtilsTest -->
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>{{figureTitle}}</title>
+{% if plotlyJsLocation is not null %}
+    <script src="{{plotlyJsLocation}}"></script>
+{% else %}
+    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
+{% endif %}
+</head>
+<body>
+{% if targetDiv is not null %}
+    {{targetDiv | raw}}
+{% endif %}
+{% if figureScript is not null %}
+    {{figureScript | raw}}
+{% endif %}
+
+</body>
+</html>
\ No newline at end of file