Repository: jtablesaw/tablesaw
Pull request #690 (2019-10-23 19:49:54)

Adds Figure.setLayout and Figure.setConfig
Thanks for contributing.

- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

The jsplot APIs don't allow a whole lot of UI flexibility. Allowing callers to send a `Layout` instance would help a lot with UI customizations. The most basic customization being changing the chart width/height.

This PR adds some simple methods to accomplish this for `VerticalBarPlot` and `HorizontalBarPlot`.

I am open to suggestions on this.

I see that the `api` package is supposed to create an API boundary so I'm not certain making `Layout` public API is the best solution. It doesn't seem such a bad solution though since the APIs already expose `tech.tablesaw.plotly.components.Figure` and `Layout.BarMode` so the `components` package isn't entirely hidden.

One idea would be to create a separate `tech.tablesaw.plotly.api.LayoutOptions` of sorts but it would more or less be a copy paste of what `components.Layout` has and in the end we would just add all the options into the main `Layout` instance.

If this idea is good (or we settle on something else) I can go through the other plot classes (`api.LinePlot`, etc.) and add methods with `Layout`.

## Testing

Manually tested.


lwhite1: I have not had time to look at this yet, but the API code is really just
intended for quick-and-dirty exploratory plots.  It's not even very good
for that.

Some kind of concise builder pattern would probably be best but I haven't
had time for that. Also, it would probably be wise to look at the Plot.ly
python enhancements (not directly implemented in the JS) that make it easy
to create more sophisticated plot types.



On Wed, Oct 23, 2019 at 3:49 PM Emilian Bold <notifications@github.com>
wrote:

> Thanks for contributing.
>
>    - Tick to sign-off your agreement to the Developer Certificate of
>    Origin (DCO) 1.1 <https://developercertificate.org>
>
> Description
>
> The jsplot APIs don't allow a whole lot of UI flexibility. Allowing
> callers to send a Layout instance would help a lot with UI
> customizations. The most basic customization being changing the chart
> width/height.
>
> This PR adds some simple methods to accomplish this for VerticalBarPlot
> and HorizontalBarPlot.
>
> I am open to suggestions on this.
>
> I see that the api package is supposed to create an API boundary so I'm
> not certain making Layout public API is the best solution. It doesn't
> seem such a bad solution though since the APIs already expose
> tech.tablesaw.plotly.components.Figure and Layout.BarMode so the
> components package isn't entirely hidden.
>
> One idea would be to create a separate
> tech.tablesaw.plotly.api.LayoutOptions of sorts but it would more or less
> be a copy paste of what components.Layout has and in the end we would
> just add all the options into the main Layout instance.
>
> If this idea is good (or we settle on something else) I can go through the
> other plot classes (api.LinePlot, etc.) and add methods with Layout.
> Testing
>
> Manually tested.
> ------------------------------
> You can view, comment on, or merge this pull request online at:
>
>   https://github.com/jtablesaw/tablesaw/pull/690
> Commit Summary
>
>    - Allows Layout argument to Vertical- and HorizontalBarPlot
>
> File Changes
>
>    - *M* jsplot/src/main/java/tech/tablesaw/plotly/api/BarPlot.java
>    <https://github.com/jtablesaw/tablesaw/pull/690/files#diff-0> (20)
>    - *M*
>    jsplot/src/main/java/tech/tablesaw/plotly/api/HorizontalBarPlot.java
>    <https://github.com/jtablesaw/tablesaw/pull/690/files#diff-1> (10)
>    - *M*
>    jsplot/src/main/java/tech/tablesaw/plotly/api/VerticalBarPlot.java
>    <https://github.com/jtablesaw/tablesaw/pull/690/files#diff-2> (10)
>
> Patch Links:
>
>    - https://github.com/jtablesaw/tablesaw/pull/690.patch
>    - https://github.com/jtablesaw/tablesaw/pull/690.diff
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/jtablesaw/tablesaw/pull/690?email_source=notifications&email_token=AA2FPAWO2EMJONLG7E474L3QQCTGLA5CNFSM4JEIRU7KYY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4HT5NKKA>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AA2FPAWJU2VVL6OTFENCK6LQQCTGLANCNFSM4JEIRU7A>
> .
>

emilianbold: So far the API seems sufficient to nicely display data. But the 1st change is to make it look good or fit somewhere which means `Layout` tweaks and the API doesn't allow that.

I don't need (yet?) to change how any of the `Trace`s are being built. I'm happy with that part being hidden from me.

Note that all the `tech.tablesaw.plotly.components` classes (such as `Layout`, `Axis`, etc.) have builders and seem quite fit to be a public API. Maybe we could just rename the package to `tech.tablesaw.plotly.api.components`

If we add `Layout` arguments to the API then you don't really need a builder to set a few chart inputs like `table`, `xCol`, `yCol`.

I don't understand how the Plotly python enhancements would help. Do you mean I should copy the API style? If I look at a bar plot:

```python
px.bar(tips, x="sex", y="total_bill", color='smoker', barmode='group', height=400)
```

they seem to use Python named arguments. I guess we could add another method to `HorizontalBarPlot` with an `int height` argument but it would pollute the class much more than exposing `Layout`. As for each `Layout` tweak I would need to add more methods. Named arguments are quite neat in Python...
emilianbold: I noticed that although not part of the `api` package users are encouraged to use the `Layout`, etc. classes when needed in this user guide page: https://jtablesaw.github.io/tablesaw/userguide/Visualization_custom

So, I think allowing `Layout` as an argument to existing plot API classes would help users (like me) that want some basic customization and are fine with how the chart is built otherwise (no need to re-do the `Trace` instances, etc)
lwhite1: I will try to cover some of the design considerations here and then look at the code specifics.

> The jsplot APIs don't allow a whole lot of UI flexibility. Allowing callers to send a Layout instance would help a lot with UI customizations. 

Yeah, part of the problem (as you suggested elsewhere) is that api is not really a good description of the intended functionality here. It should probably have been _eda_ (exploratory data analysis) or something like that. The goal was to make it easy to do one-liners for interactive analysis. The theory was that you wouldn't worry too much about height and width, etc.

>The most basic customization being changing the chart width/height.

Absolutely. My experience as a Tablesaw user, even doing interactive analysis, is exactly that. 

OTOH, when I recently worked on a project where I made hundreds of plots, I ended up writing code to wrap the builders, providing certain defaults, and making it easier to construct more complex graphs.  

For example: I frequently wanted to make scatter plots of more than two variables. With Tablesaw, you can do up to 5 numeric variables and two categorical variables: (a 3D bubble chart (x, y, z, plus bubble size, with color spectrum for the fifth numeric variable, plus shape and 'conditioning' (i.e., making separate plots for each item in a category) for two categorical variables). Or you could do 4 numeric and three categorical, or whatever.  

A problem with the current implementation is that doing any of this involves heavy use of the builders. While they're easy, they're not concise. This is what led me to believe that a different API was desirable.  It should be possible to change the dimensions and create a 2D scatter with color or shape categories without digging into the marker builder. And it should be easy to specify "use color for the category" in one plot and "use shape for the category" in another. 

> If we add Layout arguments to the API then you don't really need a builder to set a few chart inputs like table, xCol, yCol.

That's true, but you will double the size of the API by doing that (or break the existing api) because you can't add an optional argument in java. The API is already pretty large and not very elegant (in the sense of providing a lot of power through a few, uniformly applied constructs). 
 
[Sorry. Have to break this off at this point as i need to be driving. Will come back to it as time allows]
emilianbold: > This is what led me to believe that a different API was desirable. It should be possible to change the dimensions and create a 2D scatter with color or shape categories without digging into the marker builder. And it should be easy to specify "use color for the category" in one plot and "use shape for the category" in another.

You thought about this much more than me, clearly.

What you are saying makes sense: the plotly configuration is too low level, even if we wrap it in nice fluent builders. We could offer another high-level API where users can customize their charts without getting into the plotly implementation details.

I don't have a feeling about how that could look though. If you give me some pointers I can think about it and get back to you. Seems like it might depend on chart type, as in, determine some usual chart "templates"/use-cases which we could support.

Speaking of APIs, what you want would be a 3rd API package since we can't remove the existing `api` package nor the builders.

As for the `api` package methods, adding `Layout` (and `Config`!) does double the methods but each `*Plot` class has 1-3 methods so getting to 2-6 methods doesn't seem to make such a mess.

In a way what I'm trying to avoid with the `Layout` parameter is similar to your idea: I don't want to deal with the low level `Trace`, `Marker` and `TableSliceGroup`, I just want to spruce things up.
lwhite1: > What you are saying makes sense: the plotly configuration is too low level, even if we wrap it in nice fluent builders. We could offer another high-level API where users can customize their charts without getting into the plotly implementation details.
> 
> I don't have a feeling about how that could look though. If you give me some pointers I can think about it and get back to you. Seems like it might depend on chart type, as in, determine some usual chart "templates"/use-cases which we could support.
> 

There are a few different perspectives that might be worth looking at. Let me give it a little thought/research and get back to you.  

> Speaking of APIs, what you want would be a 3rd API package since we can't remove the existing `api` package nor the builders.

True, but I would deprecate the current API if we had something better. 

Having the low-level stuff accessible via builders is fine, I think, because someone will always want to create something that leverages all the flexibility you get in Plot.ly. We just don't want to make people build everything from the ground up if they just want to plot X by Y. 

> As for the `api` package methods, adding `Layout` (and `Config`!) does double the methods but each `*Plot` class has 1-3 methods so getting to 2-6 methods doesn't seem to make such a mess.

I was thinking more about adding layout to, say, the BoxPlot class. BoxPlot looks like this:

```
  public static Figure create(String title, Table table, String groupCol, String numCol) {

    Layout layout = Layout.builder().title(title).height(HEIGHT).width(WIDTH).build();

    BoxTrace trace =
        BoxTrace.builder(table.categoricalColumn(groupCol), table.nCol(numCol)).build();
    return new Figure(layout, trace);
  }

```
To add a custom layout you'd need a new method here. 

You're suggesting adding a layout to Plot, but that seems a little awkward (although easier to implement). If I were looking for a shortcut here, I would make Figure have a layout setter that overrides the default layout and leave Plot the way it is. 

> In a way what I'm trying to avoid with the `Layout` parameter is similar to your idea: I don't want to deal with the low level `Trace`, `Marker` and `TableSliceGroup`, I just want to spruce things up.

I would be ok with a PR that lets you create a plot with the existing API classes like BoxPlot and override the Layout as described above. What do you think of that option?

lwhite1: I suspect @ryancerf's analytical query module does this. I will take a look. 
lwhite1: just wanted to add that one model for a simpler api might be the "plotly express" code
https://plot.ly/python/plotly-express/
emilianbold: > If I were looking for a shortcut here, I would make Figure have a layout setter that overrides the default layout and leave Plot the way it is.

That was an idea I also had then I saw the `final Layout layout` in `Figure` and thought the API might be better. But I guess a `setLayout` in Figure would also do. Let me update the PR.
emilianbold: I've changed this branch to only add `Figure.setLayout`. Please squash the commits, no need to keep that history.

> just wanted to add that one model for a simpler api might be the "plotly express" code
https://plot.ly/python/plotly-express/

I'll look into this.
emilianbold: Actually, there's no need for `Figure` to be sight a tight class. Its whole purpose is to generate that `asJavascript` but by making more fields accessible / modifiable I think it can help with customizations. What do you think @lwhite1?

Patch:
diff --git a/aggregate/pom.xml b/aggregate/pom.xml
index ca75770c..12eaab4a 100644
--- a/aggregate/pom.xml
+++ b/aggregate/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-aggregate</artifactId>
@@ -16,37 +16,37 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-beakerx</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-excel</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-html</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-json</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-jsplot</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-saw</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
   </dependencies>
 </project>
diff --git a/beakerx/pom.xml b/beakerx/pom.xml
index d8c3abfe..5f70b0f6 100644
--- a/beakerx/pom.xml
+++ b/beakerx/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-beakerx</artifactId>
@@ -71,12 +71,12 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-docs</artifactId> <!-- An ugly workaround to make sure the docs project isn't built last so that skipNexusStagingDeployMojo works -->
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/core/pom.xml b/core/pom.xml
index 6592522c..4475056d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>tech.tablesaw</groupId>
         <artifactId>tablesaw-parent</artifactId>
-        <version>0.37.1-SNAPSHOT</version>
+        <version>0.37.4-SNAPSHOT</version>
     </parent>
 
     <artifactId>tablesaw-core</artifactId>
diff --git a/core/src/main/java/tech/tablesaw/api/BooleanColumn.java b/core/src/main/java/tech/tablesaw/api/BooleanColumn.java
index 3b7d7ae3..77bac183 100644
--- a/core/src/main/java/tech/tablesaw/api/BooleanColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/BooleanColumn.java
@@ -33,7 +33,6 @@ import it.unimi.dsi.fastutil.ints.IntComparator;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Objects;
 import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -669,19 +668,6 @@ public class BooleanColumn extends AbstractColumn<BooleanColumn, Boolean>
     return Boolean.compare(o1, o2);
   }
 
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    BooleanColumn that = (BooleanColumn) o;
-    return Objects.equals(data, that.data);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(data);
-  }
-
   private static class BooleanColumnIterator implements Iterator<Boolean> {
 
     private final ByteIterator iterator;
diff --git a/core/src/main/java/tech/tablesaw/api/ColumnType.java b/core/src/main/java/tech/tablesaw/api/ColumnType.java
index 0c997251..24d20d44 100644
--- a/core/src/main/java/tech/tablesaw/api/ColumnType.java
+++ b/core/src/main/java/tech/tablesaw/api/ColumnType.java
@@ -1,5 +1,8 @@
 package tech.tablesaw.api;
 
+import com.google.common.base.Preconditions;
+import java.util.HashMap;
+import java.util.Map;
 import tech.tablesaw.columns.AbstractColumnParser;
 import tech.tablesaw.columns.Column;
 import tech.tablesaw.columns.SkipColumnType;
@@ -19,6 +22,8 @@ import tech.tablesaw.io.ReadOptions;
 
 public interface ColumnType {
 
+  final Map<String, ColumnType> values = new HashMap<>();
+
   // standard column types
   ShortColumnType SHORT = ShortColumnType.instance();
   IntColumnType INTEGER = IntColumnType.instance();
@@ -34,6 +39,24 @@ public interface ColumnType {
   TextColumnType TEXT = TextColumnType.instance();
   SkipColumnType SKIP = SkipColumnType.instance();
 
+  static void register(ColumnType type) {
+    values.put(type.name(), type);
+  }
+
+  static ColumnType[] values() {
+    return values.values().toArray(new ColumnType[0]);
+  }
+
+  static ColumnType valueOf(String name) {
+    Preconditions.checkNotNull(name);
+
+    ColumnType result = values.get(name);
+    if (result == null) {
+      throw new IllegalArgumentException(name + " is not a registered column type.");
+    }
+    return result;
+  }
+
   Column<?> create(String name);
 
   String name();
diff --git a/core/src/main/java/tech/tablesaw/api/DoubleColumn.java b/core/src/main/java/tech/tablesaw/api/DoubleColumn.java
index 60170cd9..075b6277 100644
--- a/core/src/main/java/tech/tablesaw/api/DoubleColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/DoubleColumn.java
@@ -149,7 +149,8 @@ public class DoubleColumn extends NumberColumn<DoubleColumn, Double>
 
   @Override
   public Double get(int index) {
-    return getDouble(index);
+    double result = getDouble(index);
+    return isMissingValue(result) ? null : result;
   }
 
   @Override
diff --git a/core/src/main/java/tech/tablesaw/api/FloatColumn.java b/core/src/main/java/tech/tablesaw/api/FloatColumn.java
index 00990d63..dd28e828 100644
--- a/core/src/main/java/tech/tablesaw/api/FloatColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/FloatColumn.java
@@ -81,7 +81,8 @@ public class FloatColumn extends NumberColumn<FloatColumn, Float> {
 
   @Override
   public Float get(int index) {
-    return data.getFloat(index);
+    float result = getFloat(index);
+    return isMissingValue(result) ? null : result;
   }
 
   public static boolean valueIsMissing(float value) {
diff --git a/core/src/main/java/tech/tablesaw/api/IntColumn.java b/core/src/main/java/tech/tablesaw/api/IntColumn.java
index 819d2ae2..145399f0 100644
--- a/core/src/main/java/tech/tablesaw/api/IntColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/IntColumn.java
@@ -101,7 +101,8 @@ public class IntColumn extends NumberColumn<IntColumn, Integer>
 
   @Override
   public Integer get(int index) {
-    return getInt(index);
+    int result = getInt(index);
+    return isMissingValue(result) ? null : result;
   }
 
   @Override
diff --git a/core/src/main/java/tech/tablesaw/api/LongColumn.java b/core/src/main/java/tech/tablesaw/api/LongColumn.java
index facd8465..a9ad34e7 100644
--- a/core/src/main/java/tech/tablesaw/api/LongColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/LongColumn.java
@@ -103,7 +103,8 @@ public class LongColumn extends NumberColumn<LongColumn, Long> implements Catego
 
   @Override
   public Long get(int index) {
-    return getLong(index);
+    long result = getLong(index);
+    return isMissingValue(result) ? null : result;
   }
 
   @Override
diff --git a/core/src/main/java/tech/tablesaw/api/ShortColumn.java b/core/src/main/java/tech/tablesaw/api/ShortColumn.java
index 1dc40b8c..1bc9a317 100644
--- a/core/src/main/java/tech/tablesaw/api/ShortColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/ShortColumn.java
@@ -74,7 +74,8 @@ public class ShortColumn extends NumberColumn<ShortColumn, Short>
 
   @Override
   public Short get(int index) {
-    return getShort(index);
+    short result = getShort(index);
+    return isMissingValue(result) ? null : result;
   }
 
   public short getShort(int index) {
diff --git a/core/src/main/java/tech/tablesaw/columns/AbstractColumnType.java b/core/src/main/java/tech/tablesaw/columns/AbstractColumnType.java
index 707c6371..82be3d51 100644
--- a/core/src/main/java/tech/tablesaw/columns/AbstractColumnType.java
+++ b/core/src/main/java/tech/tablesaw/columns/AbstractColumnType.java
@@ -30,6 +30,7 @@ public abstract class AbstractColumnType implements ColumnType {
     this.byteSize = byteSize;
     this.name = name;
     this.printerFriendlyName = printerFriendlyName;
+    ColumnType.register(this);
   }
 
   @Override
diff --git a/core/src/main/java/tech/tablesaw/columns/Column.java b/core/src/main/java/tech/tablesaw/columns/Column.java
index a988bdf9..509d7fd0 100644
--- a/core/src/main/java/tech/tablesaw/columns/Column.java
+++ b/core/src/main/java/tech/tablesaw/columns/Column.java
@@ -363,15 +363,7 @@ public interface Column<T> extends Iterable<T>, Comparator<T> {
    */
   default <R, C extends Column<R>> C map(
       Function<? super T, ? extends R> fun, Function<String, C> creator) {
-    C into = creator.apply(name());
-    for (int i = 0; i < size(); i++) {
-      if (isMissing(i)) {
-        into.appendMissing();
-      } else {
-        into.append(fun.apply(get(i)));
-      }
-    }
-    return into;
+    return mapInto(fun, creator.apply(name()));
   }
 
   Column<T> setMissing(int i);
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java b/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java
index 5ad23a60..f2faba90 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java
@@ -385,6 +385,34 @@ public interface NumberMapFunctions {
     return newColumn;
   }
 
+  /** Returns a new column with a cumulative maximum calculated */
+  default DoubleColumn cumMax() {
+    double max = DoubleColumnType.missingValueIndicator();
+    DoubleColumn newColumn = DoubleColumn.create(name() + "[cumMax]", size());
+    for (int i = 0; i < size(); i++) {
+      double value = getDouble(i);
+      if (!DoubleColumnType.valueIsMissing(value)) {
+        max = DoubleColumnType.valueIsMissing(max) ? value : Math.max(max, value);
+      }
+      newColumn.set(i, max);
+    }
+    return newColumn;
+  }
+
+  /** Returns a new column with a cumulative maximum calculated */
+  default DoubleColumn cumMin() {
+    double min = DoubleColumnType.missingValueIndicator();
+    DoubleColumn newColumn = DoubleColumn.create(name() + "[cumMin]", size());
+    for (int i = 0; i < size(); i++) {
+      double value = getDouble(i);
+      if (!DoubleColumnType.valueIsMissing(value)) {
+        min = DoubleColumnType.valueIsMissing(min) ? value : Math.min(min, value);
+      }
+      newColumn.set(i, min);
+    }
+    return newColumn;
+  }
+
   /** Returns a new column with a percent change calculated */
   default DoubleColumn pctChange() {
     DoubleColumn newColumn = DoubleColumn.create(name() + "[pctChange]", size());
diff --git a/core/src/main/java/tech/tablesaw/table/Relation.java b/core/src/main/java/tech/tablesaw/table/Relation.java
index 6d6625a9..5f174ecc 100644
--- a/core/src/main/java/tech/tablesaw/table/Relation.java
+++ b/core/src/main/java/tech/tablesaw/table/Relation.java
@@ -241,21 +241,16 @@ public abstract class Relation implements Iterable<Row> {
   }
 
   public Table summary() {
-    Table summaryTable = null;
-
+    Table summaryTable = Table.create(this.name());
+    if (this.columnCount() == 0) {
+      return summaryTable;
+    }
+    summaryTable.addColumns(StringColumn.create("Measure"));
     for (int i = 0; i < this.columnCount(); i++) {
       Table columnSummary = this.column(i).summary();
       columnSummary.column(1).setName(this.column(i).name());
-
-      if (summaryTable == null) {
-        // First column, so just create the table from this
-        summaryTable = columnSummary;
-        summaryTable.setName(this.name());
-      } else {
-        String name = summaryTable.column(0).name();
-        summaryTable =
-            summaryTable.joinOn(name).fullOuter(columnSummary, columnSummary.column(0).name());
-      }
+      summaryTable =
+          summaryTable.joinOn("Measure").fullOuter(columnSummary, columnSummary.column(0).name());
     }
     summaryTable.column(0).setName("Summary");
     return summaryTable;
diff --git a/docs-src/dist/userguide/importing_data.md b/docs-src/dist/userguide/importing_data.md
index 50515d5c..55cc97b2 100644
--- a/docs-src/dist/userguide/importing_data.md
+++ b/docs-src/dist/userguide/importing_data.md
@@ -39,7 +39,7 @@ This method supplies defaults for everything but the filename. We assume that co
 You can create an options object with a builder:
 
 ```Java
-CsvReadOptionsBuilder builder = 
+CsvReadOptions.Builder builder = 
 	CsvReadOptions.builder("myFile.csv")
 		.separator('\t')										// table is tab-delimited
 		.header(false)											// no header
diff --git a/docs-src/main/userguide/importing_data.md b/docs-src/main/userguide/importing_data.md
index 76aab21e..dcf271db 100644
--- a/docs-src/main/userguide/importing_data.md
+++ b/docs-src/main/userguide/importing_data.md
@@ -4,7 +4,7 @@
 
 ## Table of contents
 
-[TOC]
+[TOC](toc)
 
 ## Supported Formats
 
@@ -40,11 +40,11 @@ This method supplies defaults for everything but the filename. We assume that co
 You can create an options object with a builder:
 
 ```Java
-CsvReadOptionsBuilder builder = 
+CsvReadOptions.Builder builder = 
 	CsvReadOptions.builder("myFile.csv")
-		.separator('\t')										// table is tab-delimited
-		.header(false)											// no header
-		.dateFormat("yyyy.MM.dd");  				// the date format to use. 
+		.separator('\t')             // table is tab-delimited
+		.header(false)               // no header
+		.dateFormat("yyyy.MM.dd");   // the date format to use. 
 
 CsvReadOptions options = builder.build();
 
@@ -193,7 +193,9 @@ Table restaurants = Table.read()
 
 It's equally easy to create a table from the results of a database query. In this case, you never need to specify the column types, because they are inferred from the database column types. 
 
-    Table t = Table.read().db(ResultSet resultSet, String tableName);
+```Java
+Table t = Table.read().db(ResultSet resultSet, String tableName);
+```
 
 Here’s a more complete example that  includes the JDBC setup:
 
@@ -235,6 +237,24 @@ Tablesaw supports importing data from HTML, JSON, and Excel. See the Javadoc for
 </dependency>
 ```
 
+#### Excel
+
+After importing the tablesaw-Excel dependency, you can use the following to read all the sheets in excel.
+
+```Java
+XlsxReadOptions options = XlsxReadOptions.builder("<excelFileName>.xlsx").build();
+XlsxReader xlsxReader = new XlsxReader();
+List<Table> tables=  xlsxReader.readMultiple(options);
+```
+
+Paricular sheet can be read using `.sheetIndex(int <index>)`:
+
+```Java
+XlsxReadOptions options = XlsxReadOptions.builder("<excelFileName>.xlsx").sheetIndex(1).build();
+XlsxReader xlsxReader = new XlsxReader();
+List<Table> tables=  xlsxReader.readMultiple(options);
+```
+
 ## Exporting data
 
 
diff --git a/docs-src/pom.xml b/docs-src/pom.xml
index f69d278a..92c1f87b 100644
--- a/docs-src/pom.xml
+++ b/docs-src/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>tech.tablesaw</groupId>
         <artifactId>tablesaw-parent</artifactId>
-        <version>0.37.1-SNAPSHOT</version>
+        <version>0.37.4-SNAPSHOT</version>
     </parent>
 
     <artifactId>tablesaw-docs</artifactId>
@@ -87,7 +87,7 @@
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-core</artifactId>
-            <version>0.37.1-SNAPSHOT</version>
+            <version>0.37.4-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
     </dependencies>
diff --git a/docs/userguide/importing_data.md b/docs/userguide/importing_data.md
index 76aab21e..b8de8d5f 100644
--- a/docs/userguide/importing_data.md
+++ b/docs/userguide/importing_data.md
@@ -40,7 +40,7 @@ This method supplies defaults for everything but the filename. We assume that co
 You can create an options object with a builder:
 
 ```Java
-CsvReadOptionsBuilder builder = 
+CsvReadOptions.Builder builder = 
 	CsvReadOptions.builder("myFile.csv")
 		.separator('\t')										// table is tab-delimited
 		.header(false)											// no header
diff --git a/excel/pom.xml b/excel/pom.xml
index 601e7763..e85dfa55 100644
--- a/excel/pom.xml
+++ b/excel/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-excel</artifactId>
@@ -82,7 +82,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java b/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java
index f17d00c9..51f1e02e 100644
--- a/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java
+++ b/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java
@@ -253,7 +253,7 @@ public class XlsxReader implements DataReader<XlsxReadOptions> {
         headerNames.add("col" + col);
       }
     }
-    Table table = Table.create(options.tableName());
+    Table table = Table.create(options.tableName() + "#" + sheet.getSheetName());
     List<Column<?>> columns = new ArrayList<>(Collections.nCopies(headerNames.size(), null));
     for (int rowNum = tableArea.startRow; rowNum <= tableArea.endRow; rowNum++) {
       row = sheet.getRow(rowNum);
diff --git a/html/pom.xml b/html/pom.xml
index 31257a8c..186b6934 100644
--- a/html/pom.xml
+++ b/html/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-html</artifactId>
@@ -81,7 +81,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/json/pom.xml b/json/pom.xml
index 0800b297..e4184d3a 100644
--- a/json/pom.xml
+++ b/json/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-json</artifactId>
@@ -81,7 +81,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/jsplot/pom.xml b/jsplot/pom.xml
index 35535032..257b7ac0 100644
--- a/jsplot/pom.xml
+++ b/jsplot/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>tech.tablesaw</groupId>
         <artifactId>tablesaw-parent</artifactId>
-        <version>0.37.1-SNAPSHOT</version>
+        <version>0.37.4-SNAPSHOT</version>
     </parent>
 
     <artifactId>tablesaw-jsplot</artifactId>
@@ -68,6 +68,7 @@
                     <artifactSet>
                         <includes>
                             <include>io.pebbletemplates:pebble</include>
+                            <include>org.unbescape:unbescape</include>
                             <include>com.coverity.security:coverity-escapers</include>
                             <include>org.slf4j:slf4j-api</include>
                         </includes>
@@ -88,7 +89,7 @@
     <dependencies>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-core</artifactId>
+            <artifactId>jackson-databind</artifactId>
         </dependency>
         <dependency>
             <groupId>io.pebbletemplates</groupId>
@@ -98,7 +99,7 @@
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-core</artifactId>
-            <version>0.37.1-SNAPSHOT</version>
+            <version>0.37.4-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
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 3bf8c386..32b5d92b 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/api/TimeSeriesPlot.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/api/TimeSeriesPlot.java
@@ -3,6 +3,7 @@ package tech.tablesaw.plotly.api;
 import java.util.List;
 import tech.tablesaw.api.DateColumn;
 import tech.tablesaw.api.DateTimeColumn;
+import tech.tablesaw.api.InstantColumn;
 import tech.tablesaw.api.NumericColumn;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.plotly.components.Figure;
@@ -56,6 +57,13 @@ public class TimeSeriesPlot {
     return new Figure(layout, trace);
   }
 
+  public static Figure create(
+      String title, String xTitle, InstantColumn xCol, String yTitle, NumericColumn<?> yCol) {
+    Layout layout = Layout.builder(title, xTitle, yTitle).build();
+    ScatterTrace trace = ScatterTrace.builder(xCol, yCol).mode(ScatterTrace.Mode.LINE).build();
+    return new Figure(layout, trace);
+  }
+
   /**
    * Creates a time series where the x values are from a DateTimeColumn, rather than a DateColumn
    *
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Component.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Component.java
index bf5d5f09..62c720f4 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Component.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Component.java
@@ -1,5 +1,8 @@
 package tech.tablesaw.plotly.components;
 
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
 import com.mitchellbosecke.pebble.PebbleEngine;
 import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.template.PebbleTemplate;
@@ -11,18 +14,46 @@ import java.util.Map;
 
 public abstract class Component {
 
-  protected final PebbleEngine engine = TemplateUtils.getNewEngine();
+  protected static final ObjectMapper mapper = new ObjectMapper();
 
+  {
+    mapper.enable(SerializationFeature.INDENT_OUTPUT);
+    mapper.setSerializationInclusion(Include.NON_NULL);
+    mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
+  }
+
+  private final PebbleEngine engine = TemplateUtils.getNewEngine();
+
+  protected PebbleEngine getEngine() {
+    return engine;
+  }
+
+  @Deprecated
   public abstract String asJavascript();
 
+  @Deprecated
   protected abstract Map<String, Object> getContext();
 
+  protected Map<String, Object> getJSONContext() {
+    return null;
+  }
+
+  public String asJSON() {
+    StringWriter w = new StringWriter();
+    try {
+      mapper.writeValue(w, getJSONContext());
+    } catch (IOException ioe) {
+      throw new UncheckedIOException(ioe);
+    }
+    return w.toString();
+  }
+
   protected String asJavascript(String filename) {
     Writer writer = new StringWriter();
     PebbleTemplate compiledTemplate;
 
     try {
-      compiledTemplate = engine.getTemplate(filename);
+      compiledTemplate = getEngine().getTemplate(filename);
       compiledTemplate.evaluate(writer, getContext());
     } catch (PebbleException e) {
       throw new IllegalStateException(e);
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Figure.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Figure.java
index 0db3fde5..47aa3b9d 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Figure.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Figure.java
@@ -26,10 +26,10 @@ import tech.tablesaw.plotly.traces.Trace;
  */
 public class Figure {
 
-  private final Trace[] data;
-  private final Layout layout;
-  private final Config config;
-  private final EventHandler[] eventHandlers;
+  private Trace[] data;
+  private Layout layout;
+  private Config config;
+  private EventHandler[] eventHandlers;
 
   private final Map<String, Object> context = new HashMap<>();
 
@@ -80,6 +80,34 @@ public class Figure {
     return layout;
   }
 
+  public void setLayout(Layout layout) {
+    this.layout = layout;
+  }
+
+  public Config getConfig() {
+    return config;
+  }
+
+  public void setConfig(Config config) {
+    this.config = config;
+  }
+
+  public EventHandler[] getEventHandlers() {
+    return eventHandlers;
+  }
+
+  public void setEventHandlers(EventHandler... handlers) {
+    eventHandlers = handlers;
+  }
+
+  public Trace[] getTraces() {
+    return data;
+  }
+
+  public void setTraces(Trace... data) {
+    this.data = data;
+  }
+
   public String asJavascript(String divName) {
     Writer writer = new StringWriter();
     PebbleTemplate compiledTemplate;
@@ -97,7 +125,7 @@ public class Figure {
     return writer.toString();
   }
 
-  private void buildContext(String divName) {
+  protected void buildContext(String divName) {
 
     String targetName = "target_" + divName;
     context.put("divName", divName);
@@ -124,7 +152,7 @@ public class Figure {
     context.put("eventHandlerFunction", eventHandlerFunction(targetName, divName));
   }
 
-  private String plotFunction(String divName) {
+  protected String plotFunction(String divName) {
     StringBuilder builder = new StringBuilder();
 
     builder.append("var data = [ ");
@@ -152,7 +180,7 @@ public class Figure {
     return builder.toString();
   }
 
-  private String eventHandlerFunction(String targetName, String divName) {
+  protected String eventHandlerFunction(String targetName, String divName) {
     StringBuilder builder = new StringBuilder();
 
     if (eventHandlers != null) {
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Font.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Font.java
index a83e62c1..6ea12171 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Font.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Font.java
@@ -1,5 +1,6 @@
 package tech.tablesaw.plotly.components;
 
+import com.fasterxml.jackson.annotation.JsonValue;
 import com.google.common.base.Preconditions;
 import java.util.HashMap;
 import java.util.Map;
@@ -29,6 +30,7 @@ public class Font extends Component {
       this.value = value;
     }
 
+    @JsonValue
     @Override
     public String toString() {
       return value;
@@ -53,7 +55,7 @@ public class Font extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("font_template.html");
+    return asJSON();
   }
 
   @Override
@@ -65,6 +67,11 @@ public class Font extends Component {
     return context;
   }
 
+  @Override
+  protected Map<String, Object> getJSONContext() {
+    return getContext();
+  }
+
   public static class FontBuilder {
 
     private Family fontFamily = Family.OPEN_SANS;
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Gradient.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Gradient.java
index 8de54c01..1542d8b1 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Gradient.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Gradient.java
@@ -1,8 +1,8 @@
 package tech.tablesaw.plotly.components;
 
+import com.fasterxml.jackson.annotation.JsonValue;
 import java.util.HashMap;
 import java.util.Map;
-import tech.tablesaw.plotly.Utils;
 
 public class Gradient extends Component {
 
@@ -19,6 +19,7 @@ public class Gradient extends Component {
       this.value = value;
     }
 
+    @JsonValue
     @Override
     public String toString() {
       return value;
@@ -35,7 +36,12 @@ public class Gradient extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("gradient_template.html");
+    return asJSON();
+  }
+
+  @Override
+  protected Map<String, Object> getJSONContext() {
+    return getContext();
   }
 
   @Override
@@ -44,9 +50,9 @@ public class Gradient extends Component {
     context.put("type", type);
     if (color != null && color.length > 0) {
       if (color.length > 1) {
-        context.put("color", Utils.dataAsString(color));
+        context.put("color", color);
       } else {
-        context.put("color", Utils.quote(color[0]));
+        context.put("color", color[0]);
       }
     }
 
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/HoverLabel.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/HoverLabel.java
index 178e591d..5ac1ae02 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/HoverLabel.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/HoverLabel.java
@@ -35,19 +35,24 @@ public class HoverLabel extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("hoverLabel_template.html");
+    return asJSON();
   }
 
   @Override
-  protected Map<String, Object> getContext() {
+  protected Map<String, Object> getJSONContext() {
     Map<String, Object> context = new HashMap<>();
-    context.put("bgColor", bgColor);
-    context.put("borderColor", borderColor);
-    context.put("nameLength", nameLength);
-    context.put("font", font);
+    context.put("bgcolor", bgColor);
+    context.put("bordercolor", borderColor);
+    context.put("namelength", nameLength);
+    context.put("font", font.getJSONContext());
     return context;
   }
 
+  @Override
+  protected Map<String, Object> getContext() {
+    return getJSONContext();
+  }
+
   public static class HoverLabelBuilder {
 
     /** Sets the background color of all hover labels on graph */
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Line.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Line.java
index a033a79a..2b1f331f 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Line.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Line.java
@@ -1,5 +1,6 @@
 package tech.tablesaw.plotly.components;
 
+import com.fasterxml.jackson.annotation.JsonValue;
 import com.google.common.base.Preconditions;
 import java.util.HashMap;
 import java.util.Map;
@@ -20,6 +21,7 @@ public class Line extends Component {
       this.value = value;
     }
 
+    @JsonValue
     @Override
     public String toString() {
       return value;
@@ -54,9 +56,14 @@ public class Line extends Component {
     return context;
   }
 
+  @Override
+  protected Map<String, Object> getJSONContext() {
+    return getContext();
+  }
+
   @Override
   public String asJavascript() {
-    return asJavascript("line_template.html");
+    return asJSON();
   }
 
   /**
@@ -78,6 +85,7 @@ public class Line extends Component {
       this.value = value;
     }
 
+    @JsonValue
     @Override
     public String toString() {
       return value;
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/Margin.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/Margin.java
index db8984d8..8bcfdfec 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/Margin.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/Margin.java
@@ -38,21 +38,26 @@ public class Margin extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("margin_template.html");
+    return asJSON();
   }
 
   @Override
   protected Map<String, Object> getContext() {
     Map<String, Object> context = new HashMap<>();
-    context.put("top", top);
-    context.put("bottom", bottom);
-    context.put("right", right);
-    context.put("left", left);
+    context.put("t", top);
+    context.put("b", bottom);
+    context.put("r", right);
+    context.put("l", left);
     context.put("pad", pad);
-    context.put("autoExpand", autoExpand);
+    context.put("autoexpand", autoExpand);
     return context;
   }
 
+  @Override
+  protected Map<String, Object> getJSONContext() {
+    return getContext();
+  }
+
   public static class MarginBuilder {
     /** The left margin, in px */
     private int left = 80;
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/change/Change.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/change/Change.java
index b69e85aa..737e86a2 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/change/Change.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/change/Change.java
@@ -13,7 +13,7 @@ public abstract class Change extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("change_template.html");
+    return asJSON();
   }
 
   Change(ChangeBuilder builder) {
@@ -22,13 +22,18 @@ public abstract class Change extends Component {
   }
 
   @Override
-  protected Map<String, Object> getContext() {
+  protected Map<String, Object> getJSONContext() {
     Map<String, Object> context = new HashMap<>();
-    if (changeLine != null) context.put("changeLine", changeLine);
-    if (fillColor != null) context.put("fillColor", fillColor);
+    if (changeLine != null) context.put("line", changeLine.getJSONContext());
+    if (fillColor != null) context.put("fillcolor", fillColor);
     return context;
   }
 
+  @Override
+  protected Map<String, Object> getContext() {
+    return getJSONContext();
+  }
+
   public static class ChangeBuilder {
 
     protected String fillColor;
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/change/ChangeLine.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/change/ChangeLine.java
index 2c81cb5d..672caaf7 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/change/ChangeLine.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/change/ChangeLine.java
@@ -20,17 +20,22 @@ public class ChangeLine extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("changeLine_template.html");
+    return asJSON();
   }
 
   @Override
-  protected Map<String, Object> getContext() {
+  protected Map<String, Object> getJSONContext() {
     Map<String, Object> context = new HashMap<>();
     if (!color.equals(DEFAULT_COLOR)) context.put("color", color);
     if (width != DEFAULT_WIDTH) context.put("width", width);
     return context;
   }
 
+  @Override
+  protected Map<String, Object> getContext() {
+    return getJSONContext();
+  }
+
   public static LineBuilder builder() {
     return new LineBuilder();
   }
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/CameraComponent.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/CameraComponent.java
index 22399d1f..9c872eed 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/CameraComponent.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/CameraComponent.java
@@ -18,7 +18,12 @@ class CameraComponent extends Component {
 
   @Override
   public String asJavascript() {
-    return asJavascript("xyz_template.html");
+    return asJSON();
+  }
+
+  @Override
+  protected Map<String, Object> getJSONContext() {
+    return getContext();
   }
 
   @Override
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/Scene.java b/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/Scene.java
index e013f86e..9041853e 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/Scene.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/components/threeD/Scene.java
@@ -54,7 +54,7 @@ public class Scene extends Component {
     Writer writer = new StringWriter();
     PebbleTemplate compiledTemplate;
     try {
-      compiledTemplate = engine.getTemplate("scene_template.html");
+      compiledTemplate = getEngine().getTemplate("scene_template.html");
       compiledTemplate.evaluate(writer, getContext());
     } catch (PebbleException e) {
       throw new IllegalStateException(e);
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/traces/BoxTrace.java b/jsplot/src/main/java/tech/tablesaw/plotly/traces/BoxTrace.java
index 4b180740..dd2d3db0 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/traces/BoxTrace.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/traces/BoxTrace.java
@@ -75,6 +75,12 @@ public class BoxTrace extends AbstractTrace {
       this.y = y;
     }
 
+    @Override
+    public BoxBuilder name(String name) {
+      super.name(name);
+      return this;
+    }
+
     BoxBuilder(CategoricalColumn<?> x, NumericColumn<? extends Number> y) {
       this.x = columnToStringArray(x);
       this.y = y.asDoubleArray();
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/traces/Scatter3DTrace.java b/jsplot/src/main/java/tech/tablesaw/plotly/traces/Scatter3DTrace.java
index b976b355..7ce39c2f 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/traces/Scatter3DTrace.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/traces/Scatter3DTrace.java
@@ -22,7 +22,6 @@ public class Scatter3DTrace extends AbstractTrace {
   private final String[] text;
   private final Mode mode;
   private final HoverLabel hoverLabel;
-  private final boolean showLegend;
   private final Marker marker;
 
   public static Scatter3DBuilder builder(double[] x, double[] y, double[] z) {
@@ -44,7 +43,6 @@ public class Scatter3DTrace extends AbstractTrace {
     this.z = builder.z;
     this.text = builder.text;
     this.hoverLabel = builder.hoverLabel;
-    this.showLegend = builder.showLegend;
     this.marker = builder.marker;
   }
 
@@ -56,7 +54,6 @@ public class Scatter3DTrace extends AbstractTrace {
     context.put("y", dataAsString(y));
     context.put("x", dataAsString(x));
     context.put("z", dataAsString(z));
-    context.put("showlegend", showLegend);
     if (marker != null) {
       context.put("marker", marker);
     }
diff --git a/jsplot/src/main/resources/changeLine_template.html b/jsplot/src/main/resources/changeLine_template.html
deleted file mode 100644
index 68aad5a3..00000000
--- a/jsplot/src/main/resources/changeLine_template.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-{% if width is not null %}
-        width: {{width}},
-{% endif %}
-{% if color is not null %}
-        color: '{{color}}',
-{% endif %}
-    }
diff --git a/jsplot/src/main/resources/change_template.html b/jsplot/src/main/resources/change_template.html
deleted file mode 100644
index 59d66530..00000000
--- a/jsplot/src/main/resources/change_template.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-{% if changeLine is not null %}
-    line: {{changeLine | raw}},
-{% endif %}
-{% if fillColor is not null %}
-    fillcolor: '{{fillColor}}',
-{% endif %}
-}
diff --git a/jsplot/src/main/resources/font_template.html b/jsplot/src/main/resources/font_template.html
deleted file mode 100644
index bb90404b..00000000
--- a/jsplot/src/main/resources/font_template.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    family: '{{family}}',
-    size: {{size}},
-    color: '{{color}}'
-}
diff --git a/jsplot/src/main/resources/gradient_template.html b/jsplot/src/main/resources/gradient_template.html
deleted file mode 100644
index 7ec815a7..00000000
--- a/jsplot/src/main/resources/gradient_template.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-    type: '{{type | raw}}',
-{% if color is not null %}
-    color: {{color | raw}},
-{% endif %}
-}
diff --git a/jsplot/src/main/resources/hoverlabel_template.html b/jsplot/src/main/resources/hoverlabel_template.html
deleted file mode 100644
index ec104ce3..00000000
--- a/jsplot/src/main/resources/hoverlabel_template.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-    namelength: {{nameLength}},
-    {% if font is not null %}
-        font: {{font.asJavascript() | raw}},
-    {% endif %}
-    bgcolor: '{{bgColor}}',
-    bordercolor: '{{borderColor}}',
-}
diff --git a/jsplot/src/main/resources/line_template.html b/jsplot/src/main/resources/line_template.html
deleted file mode 100644
index 6a1ecce9..00000000
--- a/jsplot/src/main/resources/line_template.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-    width: {{width}},
-    shape: '{{shape}}',
-{% if color is not null %}
-    color: '{{color | raw}}',
-{% endif %}
-
-    smoothing: {{smoothing}},
-    dash: '{{dash}}',
-    simplify: {{simplify}},
-}
diff --git a/jsplot/src/main/resources/margin_template.html b/jsplot/src/main/resources/margin_template.html
deleted file mode 100644
index 9d80afa0..00000000
--- a/jsplot/src/main/resources/margin_template.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-    t: {{top}},
-    b: {{bottom}},
-    l: {{left}},
-    r: {{right}},
-    pad: {{pad}},
-    autoexpand: {{autoExpand}}}
\ No newline at end of file
diff --git a/jsplot/src/main/resources/xyz_template.html b/jsplot/src/main/resources/xyz_template.html
deleted file mode 100644
index 6a5269cf..00000000
--- a/jsplot/src/main/resources/xyz_template.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-{% if x is not null %}
-    x: {{x}},
-{% endif %}
-{% if y is not null %}
-    y: {{y}},
-{% endif %}
-{% if z is not null %}
-    z: {{z}},
-{% endif %}
-}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index cdd9d5e4..3079b3a7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
 
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
     <name>Tablesaw</name>
     <description>High-performance Java Dataframe</description>
     <url>https://github.com/jtablesaw/tablesaw</url>
diff --git a/saw/pom.xml b/saw/pom.xml
index de9e1003..4f2359ca 100644
--- a/saw/pom.xml
+++ b/saw/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.37.1-SNAPSHOT</version>
+    <version>0.37.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-saw</artifactId>
@@ -81,7 +81,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.37.1-SNAPSHOT</version>
+      <version>0.37.4-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/TableAssertions.java b/core/src/test/java/tech/tablesaw/TableAssertions.java
index e3ef4971..a9836070 100644
--- a/core/src/test/java/tech/tablesaw/TableAssertions.java
+++ b/core/src/test/java/tech/tablesaw/TableAssertions.java
@@ -6,23 +6,18 @@ import tech.tablesaw.api.Table;
 
 public class TableAssertions {
   private TableAssertions() {}
-  /**
-   * Make sure each row in each table match
-   *
-   * @param compareWith the table that was sorted using some external means e.g. excel. i.e known
-   *     good data
-   * @param sortedTable the table that was sorted with Tablesaw
-   */
-  public static void assertTableEquals(Table compareWith, Table sortedTable) {
+  /** Make sure each row in each table match */
+  public static void assertTableEquals(Table expected, Table actual) {
+    assertEquals(actual.rowCount(), expected.rowCount(), "tables should have same number of rows");
     assertEquals(
-        sortedTable.rowCount(), compareWith.rowCount(), "both tables have the same number of rows");
-    int maxRows = sortedTable.rowCount();
-    int numberOfColumns = sortedTable.columnCount();
+        actual.columnCount(), expected.columnCount(), "tables should have same number of columns");
+    int maxRows = actual.rowCount();
+    int numberOfColumns = actual.columnCount();
     for (int rowIndex = 0; rowIndex < maxRows; rowIndex++) {
       for (int columnIndex = 0; columnIndex < numberOfColumns; columnIndex++) {
         assertEquals(
-            sortedTable.get(rowIndex, columnIndex),
-            compareWith.get(rowIndex, columnIndex),
+            actual.get(rowIndex, columnIndex),
+            expected.get(rowIndex, columnIndex),
             "cells[" + rowIndex + ", " + columnIndex + "] do not match");
       }
     }
diff --git a/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java b/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java
index 5cb03a0d..c04fa361 100644
--- a/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java
@@ -682,6 +682,40 @@ public class NumberColumnTest {
     }
   }
 
+  @Test
+  public void testCumMax() {
+    double[] originalValues = new double[] {MISSING, 32, 42, MISSING, 57, 52, -10, 0};
+    double[] expectedValues = new double[] {MISSING, 32, 42, 42, 57, 57, 57, 57};
+    DoubleColumn initial = DoubleColumn.create("Test", originalValues);
+    DoubleColumn cumMax = initial.cumMax();
+
+    assertEquals(
+        expectedValues.length, cumMax.size(), "Both sets of data should be the same size.");
+
+    for (int index = 0; index < cumMax.size(); index++) {
+      double actual = cumMax.getDouble(index);
+      assertEquals(
+          expectedValues[index], actual, 0, "cumMax() operation at index:" + index + " failed");
+    }
+  }
+
+  @Test
+  public void testCumMin() {
+    double[] originalValues = new double[] {MISSING, 32, 42, MISSING, 57, 52, -10, 0};
+    double[] expectedValues = new double[] {MISSING, 32, 32, 32, 32, 32, -10, -10};
+    DoubleColumn initial = DoubleColumn.create("Test", originalValues);
+    DoubleColumn cumMin = initial.cumMin();
+
+    assertEquals(
+        expectedValues.length, cumMin.size(), "Both sets of data should be the same size.");
+
+    for (int index = 0; index < cumMin.size(); index++) {
+      double actual = cumMin.getDouble(index);
+      assertEquals(
+          expectedValues[index], actual, 0, "cumMin() operation at index:" + index + " failed");
+    }
+  }
+
   @Test
   public void testSubtract2Columns() {
     double[] col1Values = new double[] {32.5, MISSING, 42, 57, 52};
@@ -711,7 +745,7 @@ public class NumberColumnTest {
         expectedValues.length, pctChange.size(), "Both sets of data should be the same size.");
 
     for (int index = 0; index < pctChange.size(); index++) {
-      double actual = pctChange.get(index);
+      double actual = pctChange.getDouble(index);
       assertEquals(
           expectedValues[index],
           actual,
@@ -723,7 +757,7 @@ public class NumberColumnTest {
   private boolean validateEquality(double[] expectedValues, DoubleColumn col) {
     assertEquals(expectedValues.length, col.size(), "Both sets of data should be the same size.");
     for (int index = 0; index < col.size(); index++) {
-      double actual = col.get(index);
+      double actual = col.getDouble(index);
       assertEquals(expectedValues[index], actual, 0.01, "value mismatch at index:" + index);
     }
     return true;
diff --git a/core/src/test/java/tech/tablesaw/api/TimeColumnTest.java b/core/src/test/java/tech/tablesaw/api/TimeColumnTest.java
index 8bb4c0f8..5632bb80 100644
--- a/core/src/test/java/tech/tablesaw/api/TimeColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/TimeColumnTest.java
@@ -30,7 +30,6 @@ import java.util.List;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.columns.Column;
-import tech.tablesaw.columns.numbers.IntColumnType;
 import tech.tablesaw.columns.times.TimeColumnType;
 import tech.tablesaw.selection.Selection;
 
@@ -253,7 +252,7 @@ public class TimeColumnTest {
     fillLargerColumn();
     IntColumn column2 = column1.minuteOfDay();
     for (int i = 0; i < column1.size() - 2; i++) {
-      assertEquals(column2.get(i), getMinuteOfDay(column1.getPackedTime(i)), 0.0001);
+      assertEquals(column2.getInt(i), getMinuteOfDay(column1.getPackedTime(i)), 0.0001);
     }
   }
 
@@ -262,7 +261,7 @@ public class TimeColumnTest {
     fillLargerColumn();
     IntColumn column2 = column1.secondOfDay();
     for (int i = 0; i < column1.size() - 2; i++) {
-      assertEquals(column2.get(i), getSecondOfDay(column1.getPackedTime(i)), 0.0001);
+      assertEquals(column2.getInt(i), getSecondOfDay(column1.getPackedTime(i)), 0.0001);
     }
   }
 
@@ -318,7 +317,7 @@ public class TimeColumnTest {
     IntColumn second = column1.second();
     assertEquals(2, second.get(0), 0.001);
     assertEquals(30, second.get(1), 0.001);
-    assertEquals(IntColumnType.missingValueIndicator(), second.get(2), 0.001);
+    assertNull(second.get(2));
   }
 
   @Test
@@ -327,7 +326,7 @@ public class TimeColumnTest {
     IntColumn minute = column1.minute();
     assertEquals(4, minute.get(0), 0.001);
     assertEquals(15, minute.get(1), 0.001);
-    assertEquals(IntColumnType.missingValueIndicator(), minute.get(2), 0.001);
+    assertNull(minute.get(2));
   }
 
   @Test
diff --git a/core/src/test/java/tech/tablesaw/columns/ColumnTest.java b/core/src/test/java/tech/tablesaw/columns/ColumnTest.java
index f89efd04..d00a0756 100644
--- a/core/src/test/java/tech/tablesaw/columns/ColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/ColumnTest.java
@@ -26,6 +26,7 @@ import java.util.function.Function;
 import java.util.function.Predicate;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import tech.tablesaw.api.BooleanColumn;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.api.DateColumn;
 import tech.tablesaw.api.DateTimeColumn;
@@ -120,6 +121,16 @@ public class ColumnTest {
     assertFalse(c.contains("foxes"));
   }
 
+  @Test
+  void testColumnDelete() {
+    final Table table =
+        Table.create(
+            BooleanColumn.create("a"), BooleanColumn.create("b"), BooleanColumn.create("c"));
+    assertEquals(3, table.columnCount());
+    table.removeColumns(table.columnNames().indexOf("b"));
+    assertEquals(2, table.columnCount());
+  }
+
   @Test
   public void testAsList() {
     Column<String> whoColumn = table.stringColumn("who");
@@ -282,7 +293,7 @@ public class ColumnTest {
   public void testMap2() {
     StringColumn c =
         DoubleColumn.create("t1", new double[] {-1, 0, 1})
-            .map(String::valueOf, StringColumn::create);
+            .map(String::valueOf, name -> StringColumn.create(name, 3));
     assertContentEquals(c, "-1.0", "0.0", "1.0");
   }
 
diff --git a/core/src/test/java/tech/tablesaw/columns/booleans/BooleanMapUtilsTest.java b/core/src/test/java/tech/tablesaw/columns/booleans/BooleanMapUtilsTest.java
index 4ff4509b..e7e7e2a0 100644
--- a/core/src/test/java/tech/tablesaw/columns/booleans/BooleanMapUtilsTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/booleans/BooleanMapUtilsTest.java
@@ -5,31 +5,31 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.api.BooleanColumn;
 
-public class BooleanMapUtilsTest {
-  private BooleanColumn singleFalse = BooleanColumn.create("", new boolean[] {false});
-  private BooleanColumn singleTrue = BooleanColumn.create("", new boolean[] {true});
+class BooleanMapUtilsTest {
+  private BooleanColumn singleFalse = BooleanColumn.create("", false);
+  private BooleanColumn singleTrue = BooleanColumn.create("", true);
 
   @Test
-  public void testAnd() {
+  void testAnd() {
     BooleanColumn actual = singleTrue.and(singleFalse);
-    assertEquals(singleFalse, actual);
+    assertEquals(singleFalse.get(0), actual.get(0));
   }
 
   @Test
-  public void testOr() {
+  void testOr() {
     BooleanColumn actual = singleFalse.or(singleTrue);
-    assertEquals(singleTrue, actual);
+    assertEquals(singleTrue.get(0), actual.get(0));
   }
 
   @Test
-  public void testAndNot() {
+  void testAndNot() {
     BooleanColumn actual = singleTrue.andNot(singleFalse);
-    assertEquals(singleTrue, actual);
+    assertEquals(singleTrue.get(0), actual.get(0));
   }
 
   @Test
-  public void testAndNot2() {
+  void testAndNot2() {
     BooleanColumn actual = singleFalse.andNot(singleTrue);
-    assertEquals(singleFalse, actual);
+    assertEquals(singleFalse.get(0), actual.get(0));
   }
 }
diff --git a/core/src/test/java/tech/tablesaw/columns/numbers/NumberMapFunctionsTest.java b/core/src/test/java/tech/tablesaw/columns/numbers/NumberMapFunctionsTest.java
index 8fb4db89..50273edf 100644
--- a/core/src/test/java/tech/tablesaw/columns/numbers/NumberMapFunctionsTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/numbers/NumberMapFunctionsTest.java
@@ -182,7 +182,7 @@ public class NumberMapFunctionsTest {
     }
     DoubleColumn newDoubles = doubles.neg();
     assertFalse(newDoubles.isEmpty());
-    assertEquals(0 - doubles.get(0), newDoubles.get(0), 0.0001);
+    assertEquals(0 - doubles.getDouble(0), newDoubles.getDouble(0), 0.0001);
   }
 
   @Test
@@ -218,7 +218,7 @@ public class NumberMapFunctionsTest {
     DoubleColumn newDoubles = doubles.square();
     DoubleColumn revert = newDoubles.sqrt();
     for (int i = 0; i < doubles.size(); i++) {
-      assertEquals(doubles.get(i), revert.get(i), 0.01);
+      assertEquals(doubles.getDouble(i), revert.getDouble(i), 0.01);
     }
   }
 
@@ -231,7 +231,7 @@ public class NumberMapFunctionsTest {
     DoubleColumn newDoubles = doubles.cube();
     DoubleColumn revert = newDoubles.cubeRoot();
     for (int i = 0; i < doubles.size(); i++) {
-      assertEquals(doubles.get(i), revert.get(i), 0.01);
+      assertEquals(doubles.getDouble(i), revert.getDouble(i), 0.01);
     }
   }
 
diff --git a/core/src/test/java/tech/tablesaw/table/TableSummaryTest.java b/core/src/test/java/tech/tablesaw/table/TableSummaryTest.java
index ca2e3855..2da8f36d 100644
--- a/core/src/test/java/tech/tablesaw/table/TableSummaryTest.java
+++ b/core/src/test/java/tech/tablesaw/table/TableSummaryTest.java
@@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.time.LocalDate;
 import org.junit.jupiter.api.Test;
+import tech.tablesaw.TableAssertions;
 import tech.tablesaw.api.BooleanColumn;
 import tech.tablesaw.api.DateColumn;
 import tech.tablesaw.api.DoubleColumn;
@@ -11,13 +12,21 @@ import tech.tablesaw.api.StringColumn;
 import tech.tablesaw.api.Table;
 
 public class TableSummaryTest {
+
+  @Test
+  public void emptyTable() {
+    Table testTable = Table.create("Data");
+    Table summary = testTable.summary();
+    TableAssertions.assertTableEquals(testTable, summary);
+  }
+
   @Test
   public void summaryTestTwoDoubleColumnsStatistics() {
     Table testTable =
         Table.create(
             "Data",
-            DoubleColumn.create("value1", new double[] {1.0, 1.1, 1.2}),
-            DoubleColumn.create("value2", new double[] {2.0, 2.1, 2.2}));
+            DoubleColumn.create("value1", 1.0, 1.1, 1.2),
+            DoubleColumn.create("value2", 2.0, 2.1, 2.2));
     Table result = testTable.summary();
     assertEquals(
         "                            Data                             \n"
@@ -39,9 +48,9 @@ public class TableSummaryTest {
     Table testTable =
         Table.create(
             "Data",
-            StringColumn.create("label", new String[] {"yellow", "yellow", "green"}),
-            DoubleColumn.create("value1", new double[] {1.0, 1.1, 1.2}),
-            BooleanColumn.create("truthy", new boolean[] {true, false, true}),
+            StringColumn.create("label", "yellow", "yellow", "green"),
+            DoubleColumn.create("value1", 1.0, 1.1, 1.2),
+            BooleanColumn.create("truthy", true, false, true),
             DateColumn.create(
                 "dates",
                 new LocalDate[] {
diff --git a/excel/src/test/java/tech/tablesaw/io/xlsx/XlsxReaderTest.java b/excel/src/test/java/tech/tablesaw/io/xlsx/XlsxReaderTest.java
index 22799072..8ef6a4c4 100644
--- a/excel/src/test/java/tech/tablesaw/io/xlsx/XlsxReaderTest.java
+++ b/excel/src/test/java/tech/tablesaw/io/xlsx/XlsxReaderTest.java
@@ -135,6 +135,7 @@ public class XlsxReaderTest {
             .read(XlsxReadOptions.builder("../data/multiplesheets.xlsx").sheetIndex(1).build());
     assertNotNull(table, "No table read from multiplesheets.xlsx");
     assertColumnValues(table.stringColumn("stringcol"), "John", "Doe");
+    assertEquals("multiplesheets.xlsx#Sheet2", table.name(), "table name is different");
 
     Table tableImplicit =
         new XlsxReader().read(XlsxReadOptions.builder("../data/multiplesheets.xlsx").build());
diff --git a/jsplot/src/test/java/tech/tablesaw/examples/HoverBroadcastExample.java b/jsplot/src/test/java/tech/tablesaw/examples/HoverBroadcastExample.java
index 74241bad..54f551bb 100644
--- a/jsplot/src/test/java/tech/tablesaw/examples/HoverBroadcastExample.java
+++ b/jsplot/src/test/java/tech/tablesaw/examples/HoverBroadcastExample.java
@@ -7,7 +7,7 @@ import tech.tablesaw.api.DoubleColumn;
 import tech.tablesaw.api.IntColumn;
 import tech.tablesaw.plotly.Plot;
 import tech.tablesaw.plotly.components.Axis;
-import tech.tablesaw.plotly.components.Figure;
+import tech.tablesaw.plotly.components.Figure.FigureBuilder;
 import tech.tablesaw.plotly.components.Layout;
 import tech.tablesaw.plotly.event.HoverBroadcastBody;
 import tech.tablesaw.plotly.event.HoverEventHandler;
@@ -60,6 +60,7 @@ public class HoverBroadcastExample {
             .yAxis2(Axis.builder().title("price").domain(0.35f, 1.0f).build())
             .build();
 
-    Plot.show(new Figure(layout, heh, trace0, trace1));
+    Plot.show(
+        new FigureBuilder().layout(layout).addEventHandlers(heh).addTraces(trace0, trace1).build());
   }
 }
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/BoxTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/BoxTest.java
index 77559753..59fcda4f 100644
--- a/jsplot/src/test/java/tech/tablesaw/plotly/BoxTest.java
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/BoxTest.java
@@ -1,12 +1,14 @@
 package tech.tablesaw.plotly;
 
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.traces.BoxTrace;
 
 @Disabled
-public class BoxTest {
+class BoxTest {
 
   private final Object[] x = {
     "sheep",
@@ -25,18 +27,23 @@ public class BoxTest {
   private final double[] y = {1, 4, 9, 16, 3, 6, 8, 8, 2, 4, 7, 11};
 
   @Test
-  public void testAsJavascript() {
+  void testAsJavascript() {
     BoxTrace trace = BoxTrace.builder(x, y).build();
-
-    System.out.println(trace.asJavascript(1));
+    assertNotNull(trace.asJavascript(1));
   }
 
   @Test
-  public void show() {
-
+  void show() {
     BoxTrace trace = BoxTrace.builder(x, y).build();
-
     Figure figure = new Figure(trace);
+    assertNotNull(figure);
     Plot.show(figure, "target");
   }
+
+  /** Test ensures that the name() method returns a BoxTraceBuilder as expected. */
+  @Test
+  void name() {
+    BoxTrace trace = BoxTrace.builder(x, y).name("my name").build();
+    assertNotNull(trace);
+  }
 }
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/Scatter3DTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/Scatter3DTest.java
index a816b473..e5632b3e 100644
--- a/jsplot/src/test/java/tech/tablesaw/plotly/Scatter3DTest.java
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/Scatter3DTest.java
@@ -1,7 +1,13 @@
 package tech.tablesaw.plotly;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import tech.tablesaw.api.DoubleColumn;
+import tech.tablesaw.api.Table;
+import tech.tablesaw.plotly.api.Scatter3DPlot;
 import tech.tablesaw.plotly.components.Axis;
 import tech.tablesaw.plotly.components.Figure;
 import tech.tablesaw.plotly.components.Layout;
@@ -19,8 +25,7 @@ public class Scatter3DTest {
   @Test
   public void testAsJavascript() {
     Scatter3DTrace trace = Scatter3DTrace.builder(x, y, z).text(labels).build();
-
-    System.out.println(trace.asJavascript(1));
+    assertNotNull(trace.asJavascript(1));
   }
 
   @Test
@@ -30,7 +35,7 @@ public class Scatter3DTest {
         Scatter3DTrace.builder(x, y, z).mode(Scatter3DTrace.Mode.MARKERS).text(labels).build();
 
     Layout layout = Layout.builder().xAxis(Axis.builder().title("x title").build()).build();
-
+    assertEquals("x title", layout.getTitle());
     Plot.show(new Figure(layout, trace));
   }
 
@@ -52,4 +57,14 @@ public class Scatter3DTest {
 
     Plot.show(new Figure(trace));
   }
+
+  @Test
+  void createScatter3D() {
+    DoubleColumn xData = DoubleColumn.create("x", new double[] {2, 2, 1});
+    DoubleColumn yData = DoubleColumn.create("y", new double[] {1, 2, 3});
+    DoubleColumn zData = DoubleColumn.create("z", new double[] {1, 4, 1});
+
+    Table data = Table.create().addColumns(xData, yData, zData);
+    assertNotNull(Scatter3DPlot.create("3D plot", data, "x", "y", "z"));
+  }
 }
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/TimeSeriesTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/TimeSeriesTest.java
new file mode 100644
index 00000000..17cc4f69
--- /dev/null
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/TimeSeriesTest.java
@@ -0,0 +1,27 @@
+package tech.tablesaw.plotly;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+import tech.tablesaw.api.Table;
+import tech.tablesaw.plotly.api.TimeSeriesPlot;
+import tech.tablesaw.plotly.components.Figure;
+
+class TimeSeriesTest {
+
+  @Test
+  void testWithInstant() throws IOException {
+
+    Table dateTable = Table.read().csv("../data/dateTimeTestFile.csv");
+    dateTable.addColumns(dateTable.dateTimeColumn(0).asInstantColumn().setName("Instant"));
+    Figure figure =
+        TimeSeriesPlot.create(
+            "Value over time",
+            "time",
+            dateTable.instantColumn("Instant"),
+            "values",
+            dateTable.numberColumn("Value"));
+    assertNotNull(figure);
+  }
+}
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/components/FigureTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/components/FigureTest.java
index ac9fa1a8..e9ad6c48 100644
--- a/jsplot/src/test/java/tech/tablesaw/plotly/components/FigureTest.java
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/components/FigureTest.java
@@ -70,20 +70,20 @@ class FigureTest {
             + "    width: 700,\n"
             + "    showlegend: true,\n"
             + "    margin: {\n"
-            + "    t: 200,\n"
-            + "    b: 80,\n"
-            + "    l: 200,\n"
-            + "    r: 80,\n"
-            + "    pad: 0,\n"
-            + "    autoexpand: true},\n"
+            + "  \"autoexpand\" : true,\n"
+            + "  \"b\" : 80,\n"
+            + "  \"l\" : 200,\n"
+            + "  \"pad\" : 0,\n"
+            + "  \"r\" : 80,\n"
+            + "  \"t\" : 200\n"
+            + "},\n"
             + "    xaxis: {\n"
             + "    title: 'x Axis 1',\n"
             + "        titlefont: {\n"
-            + "    family: 'arial',\n"
-            + "    size: 8,\n"
-            + "    color: 'red'\n"
-            + "}\n"
-            + ",\n"
+            + "  \"color\" : \"red\",\n"
+            + "  \"family\" : \"arial\",\n"
+            + "  \"size\" : 8\n"
+            + "},\n"
             + "    },\n"
             + "\n"
             + "\n"
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/CenterTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/CenterTest.java
index de595ec4..c34082b2 100644
--- a/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/CenterTest.java
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/CenterTest.java
@@ -9,11 +9,11 @@ public class CenterTest {
   private static final String javaScript =
       "{"
           + System.lineSeparator()
-          + "    x: 1.0,"
+          + "  \"x\" : 1.0,"
           + System.lineSeparator()
-          + "    y: 2.0,"
+          + "  \"y\" : 2.0,"
           + System.lineSeparator()
-          + "    z: 3.0,"
+          + "  \"z\" : 3.0"
           + System.lineSeparator()
           + "}";
 
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/EyeTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/EyeTest.java
index 298608f1..0622fec0 100644
--- a/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/EyeTest.java
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/EyeTest.java
@@ -9,11 +9,11 @@ public class EyeTest {
   private static final String javaScript =
       "{"
           + System.lineSeparator()
-          + "    x: 1.0,"
+          + "  \"x\" : 1.0,"
           + System.lineSeparator()
-          + "    y: 2.0,"
+          + "  \"y\" : 2.0,"
           + System.lineSeparator()
-          + "    z: 3.0,"
+          + "  \"z\" : 3.0"
           + System.lineSeparator()
           + "}";
 
diff --git a/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/UpTest.java b/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/UpTest.java
index 91aa5356..a27d7167 100644
--- a/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/UpTest.java
+++ b/jsplot/src/test/java/tech/tablesaw/plotly/components/threeD/UpTest.java
@@ -9,11 +9,11 @@ public class UpTest {
   private static final String javaScript =
       "{"
           + System.lineSeparator()
-          + "    x: 1.0,"
+          + "  \"x\" : 1.0,"
           + System.lineSeparator()
-          + "    y: 2.0,"
+          + "  \"y\" : 2.0,"
           + System.lineSeparator()
-          + "    z: 3.0,"
+          + "  \"z\" : 3.0"
           + System.lineSeparator()
           + "}";