All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.figure.FigureWidgetTranslator Maven / Gradle / Ivy

There is a newer version: 0.37.1
Show newest version
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.figure;

import io.deephaven.api.Selectable;
import io.deephaven.base.verify.Assert;
import io.deephaven.engine.table.PartitionedTable;
import io.deephaven.engine.table.Table;
import io.deephaven.gui.shape.JShapes;
import io.deephaven.gui.shape.NamedShape;
import io.deephaven.gui.shape.Shape;
import io.deephaven.plot.AxisImpl;
import io.deephaven.plot.BaseFigureImpl;
import io.deephaven.plot.ChartImpl;
import io.deephaven.plot.FigureWidget;
import io.deephaven.plot.Font;
import io.deephaven.plot.SeriesCollection;
import io.deephaven.plot.axistransformations.AxisTransform;
import io.deephaven.plot.axistransformations.AxisTransformBusinessCalendar;
import io.deephaven.plot.datasets.AbstractDataSeries;
import io.deephaven.plot.datasets.category.AbstractCategoryDataSeries;
import io.deephaven.plot.datasets.category.CategoryDataSeriesMap;
import io.deephaven.plot.datasets.category.CategoryTreemapDataSeriesTableMap;
import io.deephaven.plot.datasets.category.CategoryDataSeriesPartitionedTable;
import io.deephaven.plot.datasets.category.CategoryDataSeriesSwappablePartitionedTable;
import io.deephaven.plot.datasets.categoryerrorbar.CategoryErrorBarDataSeriesPartitionedTable;
import io.deephaven.plot.datasets.data.IndexableNumericData;
import io.deephaven.plot.datasets.data.IndexableNumericDataSwappableTable;
import io.deephaven.plot.datasets.data.IndexableNumericDataTable;
import io.deephaven.plot.datasets.interval.IntervalXYDataSeriesArray;
import io.deephaven.plot.datasets.multiseries.AbstractMultiSeries;
import io.deephaven.plot.datasets.multiseries.AbstractPartitionedTableHandleMultiSeries;
import io.deephaven.plot.datasets.multiseries.MultiCatSeries;
import io.deephaven.plot.datasets.multiseries.MultiOHLCSeries;
import io.deephaven.plot.datasets.multiseries.MultiXYErrorBarSeries;
import io.deephaven.plot.datasets.multiseries.MultiXYSeries;
import io.deephaven.plot.datasets.ohlc.OHLCDataSeriesArray;
import io.deephaven.plot.datasets.xy.AbstractXYDataSeries;
import io.deephaven.plot.datasets.xy.XYDataSeriesArray;
import io.deephaven.plot.datasets.xyerrorbar.XYErrorBarDataSeriesArray;
import io.deephaven.plot.util.PlotUtils;
import io.deephaven.plot.util.tables.*;
import io.deephaven.plot.util.tables.PartitionedTableHandle;
import io.deephaven.plugin.type.Exporter;
import io.deephaven.plugin.type.Exporter.Reference;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.AxisDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.BoolMapWithDefault;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.BusinessCalendarDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.BusinessCalendarDescriptor.BusinessPeriod;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.BusinessCalendarDescriptor.Holiday;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.BusinessCalendarDescriptor.LocalDate;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.DoubleMapWithDefault;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.MultiSeriesDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.MultiSeriesSourceDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.OneClickDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.SeriesDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.SeriesPlotStyle;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.SourceDescriptor;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.SourceType;
import io.deephaven.proto.backplane.script.grpc.FigureDescriptor.StringMapWithDefault;
import io.deephaven.time.calendar.BusinessCalendar;
import org.jetbrains.annotations.NotNull;
import java.time.format.DateTimeFormatter;

import java.awt.*;
import java.time.DayOfWeek;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.Stream;

public class FigureWidgetTranslator {
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");

    private final List errorList = new ArrayList<>();
    private final Map tablePositionMap = new HashMap<>();
    private final Map partitionedTablePositionMap = new HashMap<>();

    private FigureWidgetTranslator() {}

    public static FigureDescriptor translate(FigureWidget figure, Exporter exporter) {
        return new FigureWidgetTranslator().translateFigure(figure, exporter);
    }

    private FigureDescriptor translateFigure(FigureWidget f, Exporter exporter) {
        FigureDescriptor.Builder clientFigure = FigureDescriptor.newBuilder();
        BaseFigureImpl figure = f.getFigure();

        // translate tables first, so we can use them to look up tables as needed
        int i = 0;
        for (Map.Entry> entry : figure.getTableHandles().stream()
                .collect(Collectors.groupingBy(TableHandle::getTable)).entrySet()) {
            Set relevantColumns = entry.getValue().stream().map(TableHandle::getColumns).flatMap(Set::stream)
                    .collect(Collectors.toSet());
            Table table = entry.getKey().view(Selectable.from(relevantColumns));

            for (TableHandle handle : entry.getValue()) {
                tablePositionMap.put(handle, i);
            }
            i++;

            // noinspection unused
            final Reference reference = exporter.reference(table);
            // relying on FetchObjectResponse.export_id for communicating exported tables to the client
        }

        i = 0;
        for (Map.Entry> entry : figure.getPartitionedTableHandles()
                .stream().collect(Collectors.groupingBy(PartitionedTableHandle::getPartitionedTable)).entrySet()) {

            // TODO deephaven-core#2535 Restore this with a "PartitionedTableSupplier" type, if it is created
            // Set relevantColumns =
            // entry.getValue().stream().map(PartitionedTableHandle::getColumns).flatMap(Set::stream).collect(Collectors.toSet());
            // PartitionedTable partitionedTable = new PartitionedTableSupplier(entry.getKey(),
            // Collections.singletonList(t -> t.view(relevantColumns)));
            PartitionedTable partitionedTable = entry.getKey();

            for (PartitionedTableHandle handle : entry.getValue()) {
                partitionedTablePositionMap.put(handle, i);
            }
            i++;

            exporter.reference(partitionedTable);
        }

        assignOptionalField(figure.getTitle(), clientFigure::setTitle, clientFigure::clearTitle);
        assignOptionalField(toCssColorString(figure.getTitleColor()), clientFigure::setTitleColor,
                clientFigure::clearTitleColor);
        assignOptionalField(toCssFont(figure.getTitleFont()), clientFigure::setTitleFont, clientFigure::clearTitleFont);

        List charts = figure.getCharts().getCharts();

        for (ChartImpl chart : charts) {
            clientFigure.addCharts(translate(chart));
        }

        clientFigure.setCols(figure.getWidth());
        clientFigure.setRows(figure.getHeight());

        clientFigure.setUpdateInterval(figure.getUpdateInterval());

        clientFigure.addAllErrors(errorList);

        return clientFigure.build();
    }

    private static void assignOptionalStringField(Object value, Consumer setter, Runnable clear) {
        if (value != null) {
            setter.accept(value.toString());
        } else {
            clear.run();
        }
    }

    private static  void assignOptionalField(T value, Consumer setter, Runnable clear) {
        if (value != null) {
            setter.accept(value);
        } else {
            clear.run();
        }
    }

    private FigureDescriptor.ChartDescriptor translate(ChartImpl chart) {
        Assert.eq(chart.dimension(), "chart.dimensions()", 2);
        FigureDescriptor.ChartDescriptor.Builder clientChart = FigureDescriptor.ChartDescriptor.newBuilder();

        boolean swappedPositions = chart.getPlotOrientation() != ChartImpl.PlotOrientation.VERTICAL;
        Map axes = new HashMap<>();

        // x=0, y=1, z=2, unless swapped

        // The first X axis is on the bottom, later instances should be on the top. Likewise, the first Y axis
        // is on the left, and later instances appear on the right.
        AxisDescriptor.Builder firstX = null;
        AxisDescriptor.Builder firstY = null;

        for (int i = 0; i < chart.getAxis().length; i++) {
            final AxisDescriptor.AxisType type;
            if ((i == 0 && !swappedPositions) || (i == 1 && swappedPositions)) {
                type = AxisDescriptor.AxisType.X;
            } else {
                Assert.eqTrue(i == 0 || i == 1, "i == 0 || i == 1");
                type = AxisDescriptor.AxisType.Y;
            }
            List currentPositionAxes = chart.getAxis()[i];
            for (AxisImpl axis : currentPositionAxes) {
                if (axis.getType() == null) {
                    // apparently unused, yet still in the collection - skip adding it to our list
                    // so we don't consider it later
                    continue;
                }
                AxisDescriptor.Builder clientAxis = AxisDescriptor.newBuilder();
                clientAxis.setId(type.name() + axis.id());
                clientAxis.setFormatType(AxisDescriptor.AxisFormatType.valueOf(axis.getType().name()));
                clientAxis.setLog(axis.isLog());
                assignOptionalField(axis.getLabel(), clientAxis::setLabel, clientAxis::clearLabel);
                assignOptionalField(toCssFont(axis.getLabelFont()), clientAxis::setLabelFont,
                        clientAxis::clearLabelFont);
                // clientAxis.setFormat(axis.getFormat().toString());
                assignOptionalField(axis.getFormatPattern(), clientAxis::setFormatPattern,
                        clientAxis::clearFormatPattern);
                assignOptionalField(toCssColorString(axis.getColor()), clientAxis::setColor, clientAxis::clearColor);
                clientAxis.setMinRange(axis.getMinRange());
                clientAxis.setMaxRange(axis.getMaxRange());
                clientAxis.setMinorTicksVisible(axis.isMinorTicksVisible());
                clientAxis.setMajorTicksVisible(axis.isMajorTicksVisible());
                clientAxis.setMinorTickCount(axis.getMinorTickCount());
                clientAxis.setGapBetweenMajorTicks(axis.getGapBetweenMajorTicks());
                assignOptionalField(axis.getMajorTickLocations(),
                        arr -> DoubleStream.of(arr).forEach(clientAxis::addMajorTickLocations),
                        clientAxis::clearMajorTickLocations);
                // clientAxis.setAxisTransform(axis.getAxisTransform().toString());
                clientAxis.setTickLabelAngle(axis.getTickLabelAngle());
                clientAxis.setInvert(axis.getInvert());
                clientAxis.setIsTimeAxis(axis.isTimeAxis());

                final AxisTransform axisTransform = axis.getAxisTransform();
                if (axisTransform instanceof AxisTransformBusinessCalendar) {
                    clientAxis.setBusinessCalendarDescriptor(
                            translateBusinessCalendar((AxisTransformBusinessCalendar) axisTransform));
                }

                clientAxis.setType(type);
                if (type == AxisDescriptor.AxisType.X) {
                    if (firstX == null) {
                        firstX = clientAxis;
                        clientAxis.setPosition(AxisDescriptor.AxisPosition.BOTTOM);
                    } else {
                        clientAxis.setPosition(AxisDescriptor.AxisPosition.TOP);
                    }
                } else if (type == AxisDescriptor.AxisType.Y) {
                    if (firstY == null) {
                        firstY = clientAxis;
                        clientAxis.setPosition(AxisDescriptor.AxisPosition.LEFT);
                    } else {
                        clientAxis.setPosition(AxisDescriptor.AxisPosition.RIGHT);
                    }
                }

                axes.put(type.name() + axis.id(), clientAxis.build());
            }
        }
        clientChart.addAllAxes(axes.values());

        Stream.Builder clientSeriesCollection = Stream.builder();
        Stream.Builder clientMultiSeriesCollection = Stream.builder();

        chart.getAxes().forEach(axesImpl -> {

            // assign some bookkeeping axis instances just once
            AxisDescriptor xAxis = axes.get("X" + axesImpl.xAxis().id());
            AxisDescriptor yAxis = axes.get("Y" + axesImpl.yAxis().id());

            // this bookkeeping is only for category based plots
            final AxisDescriptor catAxis;
            final AxisDescriptor numAxis;
            if (xAxis.getFormatType() == AxisDescriptor.AxisFormatType.CATEGORY) {
                Assert.eq(yAxis.getFormatType(), "yAxis.getFormatType()", AxisDescriptor.AxisFormatType.NUMBER);
                catAxis = xAxis;
                numAxis = yAxis;
            } else if (yAxis.getFormatType() == AxisDescriptor.AxisFormatType.CATEGORY) {
                Assert.eq(xAxis.getFormatType(), "xAxis.getFormatType()", AxisDescriptor.AxisFormatType.NUMBER);
                catAxis = yAxis;
                numAxis = xAxis;
            } else {
                // this plot is not category based, leave these blank, they are not needed
                catAxis = null;
                numAxis = null;
            }

            // use the description map since it is known to be ordered correctly
            axesImpl.dataSeries().getSeriesDescriptions().values().stream()
                    .map(SeriesCollection.SeriesDescription::getSeries).forEach(seriesInternal -> {
                        if (seriesInternal instanceof AbstractDataSeries) {
                            SeriesDescriptor.Builder clientSeries = SeriesDescriptor.newBuilder();
                            clientSeries.setPlotStyle(
                                    FigureDescriptor.SeriesPlotStyle.valueOf(axesImpl.getPlotStyle().name()));
                            clientSeries.setName(String.valueOf(seriesInternal.name()));
                            Stream.Builder clientAxes = Stream.builder();

                            AbstractDataSeries s = (AbstractDataSeries) seriesInternal;

                            assignOptionalField(s.getLinesVisible(), clientSeries::setLinesVisible,
                                    clientSeries::clearLinesVisible);
                            assignOptionalField(s.getPointsVisible(), clientSeries::setShapesVisible,
                                    clientSeries::clearShapesVisible);
                            clientSeries.setGradientVisible(s.getGradientVisible());
                            assignOptionalField(toCssColorString(s.getLineColor()), clientSeries::setLineColor,
                                    clientSeries::clearLineColor);
                            // clientSeries.setLineStyle(s.getLineStyle().toString());
                            assignOptionalField(toCssColorString(s.getSeriesColor()), clientSeries::setShapeColor,
                                    clientSeries::clearShapeColor);
                            assignOptionalField(s.getPointLabelFormat(), clientSeries::setPointLabelFormat,
                                    clientSeries::clearPointLabelFormat);
                            assignOptionalField(s.getXToolTipPattern(), clientSeries::setXToolTipPattern,
                                    clientSeries::clearXToolTipPattern);
                            assignOptionalField(s.getYToolTipPattern(), clientSeries::setYToolTipPattern,
                                    clientSeries::clearYToolTipPattern);

                            // build the set of axes that the series is watching, and give each a type, starting
                            // with the x and y we have so far mapped to this

                            if (s instanceof AbstractXYDataSeries) {
                                // TODO #3293: Individual point shapes/sizes/labels
                                // Right now just gets one set for the whole series
                                AbstractXYDataSeries abstractSeries = (AbstractXYDataSeries) s;
                                assignOptionalStringField(abstractSeries.getPointShape(), clientSeries::setShape,
                                        clientSeries::clearShape);
                                assignOptionalField(abstractSeries.getPointSize(), clientSeries::setShapeSize,
                                        clientSeries::clearShapeSize);
                                assignOptionalField(abstractSeries.getPointLabel(), clientSeries::setShapeLabel,
                                        clientSeries::clearShapeLabel);

                                if (s instanceof IntervalXYDataSeriesArray) {
                                    // interval (aka histogram)
                                    IntervalXYDataSeriesArray series = (IntervalXYDataSeriesArray) s;
                                    clientAxes.add(makeSourceDescriptor(series.getX(), SourceType.X, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getStartX(), SourceType.X_LOW, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getEndX(), SourceType.X_HIGH, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getY(), SourceType.Y, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getStartY(), SourceType.Y_LOW, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getEndY(), SourceType.Y_HIGH, yAxis));
                                } else if (s instanceof XYErrorBarDataSeriesArray) {
                                    // errorbar x, xy
                                    XYErrorBarDataSeriesArray series = (XYErrorBarDataSeriesArray) s;
                                    clientAxes.add(makeSourceDescriptor(series.getX(), SourceType.X, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getXLow(), SourceType.X_LOW, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getXHigh(), SourceType.X_HIGH, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getY(), SourceType.Y, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getYLow(), SourceType.Y_LOW, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getYHigh(), SourceType.Y_HIGH, yAxis));
                                } else if (s instanceof OHLCDataSeriesArray) {
                                    OHLCDataSeriesArray series = (OHLCDataSeriesArray) s;
                                    clientAxes.add(makeSourceDescriptor(series.getTime(), SourceType.TIME, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getOpen(), SourceType.OPEN, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getClose(), SourceType.CLOSE, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getHigh(), SourceType.HIGH, yAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getLow(), SourceType.LOW, yAxis));
                                } else if (s instanceof XYDataSeriesArray) {
                                    // xy of some other kind
                                    XYDataSeriesArray series = (XYDataSeriesArray) s;
                                    clientAxes.add(makeSourceDescriptor(series.getX(), SourceType.X, xAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getY(), SourceType.Y, yAxis));
                                } else {
                                    // warn about other unsupported series types
                                    errorList.add("OpenAPI presently does not support series of type " + s.getClass());
                                }
                            } else if (s instanceof AbstractCategoryDataSeries) {
                                // TODO #3293: Individual point shapes/sizes/labels
                                // Right now just gets one set for the whole series
                                AbstractCategoryDataSeries abstractSeries = (AbstractCategoryDataSeries) s;
                                assignOptionalStringField(abstractSeries.getPointShape(), clientSeries::setShape,
                                        clientSeries::clearShape);
                                assignOptionalField(abstractSeries.getPointSize(), clientSeries::setShapeSize,
                                        clientSeries::clearShapeSize);
                                assignOptionalField(abstractSeries.getLabel(), clientSeries::setShapeLabel,
                                        clientSeries::clearShapeLabel);

                                if (s instanceof CategoryDataSeriesPartitionedTable) {// bar and pie from a table
                                    CategoryDataSeriesPartitionedTable series = (CategoryDataSeriesPartitionedTable) s;
                                    clientAxes
                                            .add(makeSourceDescriptor(series.getTableHandle(), series.getCategoryCol(),
                                                    catAxis == xAxis ? SourceType.X : SourceType.Y, catAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getTableHandle(), series.getValueCol(),
                                            numAxis == xAxis ? SourceType.X : SourceType.Y, numAxis));
                                } else if (s instanceof CategoryDataSeriesSwappablePartitionedTable) {
                                    CategoryDataSeriesSwappablePartitionedTable series =
                                            (CategoryDataSeriesSwappablePartitionedTable) s;

                                    clientAxes.add(
                                            makeSourceDescriptor(series.getSwappableTable(), series.getCategoryCol(),
                                                    catAxis == xAxis ? SourceType.X : SourceType.Y, catAxis));
                                    clientAxes.add(
                                            makeSourceDescriptor(series.getSwappableTable(), series.getNumericCol(),
                                                    numAxis == xAxis ? SourceType.X : SourceType.Y, numAxis));

                                } else if (s instanceof CategoryTreemapDataSeriesTableMap) {
                                    CategoryTreemapDataSeriesTableMap series = (CategoryTreemapDataSeriesTableMap) s;
                                    clientAxes
                                            .add(makeSourceDescriptor(series.getTableHandle(), series.getCategoryCol(),
                                                    catAxis == xAxis ? SourceType.X : SourceType.Y, catAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getTableHandle(),
                                            series.getParentColumn(), SourceType.PARENT, null));
                                    if (series.getValueCol() != null) {
                                        clientAxes
                                                .add(makeSourceDescriptor(series.getTableHandle(), series.getValueCol(),
                                                        numAxis == xAxis ? SourceType.X : SourceType.Y, numAxis));
                                    }
                                    if (series.getLabelColumn() != null) {
                                        clientAxes.add(makeSourceDescriptor(series.getTableHandle(),
                                                series.getLabelColumn(), SourceType.LABEL, null));
                                    }
                                    if (series.getColorColumn() != null) {
                                        clientAxes.add(makeSourceDescriptor(series.getTableHandle(),
                                                series.getColorColumn(), SourceType.COLOR, null));
                                    }
                                    if (series.getHoverTextColumn() != null) {
                                        clientAxes.add(makeSourceDescriptor(series.getTableHandle(),
                                                series.getHoverTextColumn(), SourceType.HOVER_TEXT, null));
                                    }
                                } else if (s instanceof CategoryErrorBarDataSeriesPartitionedTable) {
                                    CategoryErrorBarDataSeriesPartitionedTable series =
                                            (CategoryErrorBarDataSeriesPartitionedTable) s;
                                    clientAxes.add(
                                            makeSourceDescriptor(series.getTableHandle(), series.getCategoryColumn(),
                                                    catAxis == xAxis ? SourceType.X : SourceType.Y, catAxis));
                                    clientAxes
                                            .add(makeSourceDescriptor(series.getTableHandle(), series.getValueColumn(),
                                                    numAxis == xAxis ? SourceType.X : SourceType.Y, numAxis));
                                    clientAxes.add(
                                            makeSourceDescriptor(series.getTableHandle(), series.getErrorBarLowColumn(),
                                                    numAxis == xAxis ? SourceType.X_LOW : SourceType.Y_LOW, numAxis));
                                    clientAxes.add(makeSourceDescriptor(series.getTableHandle(),
                                            series.getErrorBarHighColumn(),
                                            numAxis == xAxis ? SourceType.X_HIGH : SourceType.Y_HIGH, numAxis));
                                } else if (s instanceof CategoryDataSeriesMap) {// bar and plot from constant data
                                    errorList.add("OpenAPI presently does not support series of type " + s.getClass());
                                }
                            }

                            clientSeries.addAllDataSources(clientAxes.build().collect(Collectors.toList()));
                            clientSeriesCollection.add(clientSeries.build());
                        } else if (seriesInternal instanceof AbstractMultiSeries) {
                            AbstractMultiSeries multiSeries = (AbstractMultiSeries) seriesInternal;

                            MultiSeriesDescriptor.Builder clientSeries = MultiSeriesDescriptor.newBuilder();
                            clientSeries.setPlotStyle(SeriesPlotStyle.valueOf(axesImpl.getPlotStyle().name()));
                            clientSeries.setName(String.valueOf(seriesInternal.name()));

                            Stream.Builder clientAxes = Stream.builder();


                            if (multiSeries instanceof AbstractPartitionedTableHandleMultiSeries) {
                                AbstractPartitionedTableHandleMultiSeries partitionedTableMultiSeries =
                                        (AbstractPartitionedTableHandleMultiSeries) multiSeries;
                                PartitionedTableHandle plotHandle =
                                        partitionedTableMultiSeries.getPartitionedTableHandle();

                                if (partitionedTableMultiSeries instanceof MultiXYSeries) {
                                    MultiXYSeries multiXYSeries = (MultiXYSeries) partitionedTableMultiSeries;
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiXYSeries.getXCol(), SourceType.X, xAxis));
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiXYSeries.getYCol(), SourceType.Y, yAxis));
                                    clientSeries.setLineColor(stringMapWithDefault(mergeColors(
                                            multiXYSeries.lineColorSeriesNameTointMap(),
                                            multiXYSeries.lineColorSeriesNameToStringMap(),
                                            multiXYSeries.lineColorSeriesNameToPaintMap())));
                                    clientSeries.setPointColor(stringMapWithDefault(mergeColors(
                                            multiXYSeries.pointColorSeriesNameTointMap(),
                                            multiXYSeries.pointColorSeriesNameToStringMap(),
                                            multiXYSeries.pointColorSeriesNameToPaintMap())));
                                    clientSeries.setLinesVisible(
                                            boolMapWithDefault(multiXYSeries.linesVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setPointsVisible(
                                            boolMapWithDefault(multiXYSeries.pointsVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setGradientVisible(
                                            boolMapWithDefault(multiXYSeries.gradientVisibleSeriesNameTobooleanMap()));
                                    clientSeries.setPointLabelFormat(stringMapWithDefault(
                                            multiXYSeries.pointLabelFormatSeriesNameToStringMap()));
                                    clientSeries.setXToolTipPattern(
                                            stringMapWithDefault(multiXYSeries.xToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setYToolTipPattern(
                                            stringMapWithDefault(multiXYSeries.yToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setPointLabel(stringMapWithDefault(
                                            multiXYSeries.pointColorSeriesNameToStringMap(), Objects::toString));
                                    clientSeries.setPointSize(doubleMapWithDefault(
                                            multiXYSeries.pointSizeSeriesNameToNumberMap(),
                                            number -> number == null ? null : number.doubleValue()));

                                    clientSeries.setPointShape(stringMapWithDefault(mergeShapes(
                                            multiXYSeries.pointShapeSeriesNameToStringMap(),
                                            multiXYSeries.pointShapeSeriesNameToShapeMap())));
                                } else if (partitionedTableMultiSeries instanceof MultiCatSeries) {
                                    MultiCatSeries multiCatSeries = (MultiCatSeries) partitionedTableMultiSeries;
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiCatSeries.getCategoryCol(),
                                            catAxis == xAxis ? SourceType.X : SourceType.Y, catAxis));
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiCatSeries.getNumericCol(),
                                            numAxis == xAxis ? SourceType.X : SourceType.Y, numAxis));
                                    clientSeries.setLineColor(stringMapWithDefault(mergeColors(
                                            multiCatSeries.lineColorSeriesNameTointMap(),
                                            multiCatSeries.lineColorSeriesNameToStringMap(),
                                            multiCatSeries.lineColorSeriesNameToPaintMap())));
                                    clientSeries.setPointColor(stringMapWithDefault(mergeColors(
                                            multiCatSeries.pointColorSeriesNameTointMap(),
                                            multiCatSeries.pointColorSeriesNameToStringMap(),
                                            multiCatSeries.pointColorSeriesNameToPaintMap())));
                                    clientSeries.setLinesVisible(
                                            boolMapWithDefault(multiCatSeries.linesVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setPointsVisible(
                                            boolMapWithDefault(multiCatSeries.pointsVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setGradientVisible(
                                            boolMapWithDefault(multiCatSeries.gradientVisibleSeriesNameTobooleanMap()));
                                    clientSeries.setPointLabelFormat(stringMapWithDefault(
                                            multiCatSeries.pointLabelFormatSeriesNameToStringMap()));
                                    clientSeries.setXToolTipPattern(stringMapWithDefault(
                                            multiCatSeries.xToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setYToolTipPattern(stringMapWithDefault(
                                            multiCatSeries.yToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setPointLabel(stringMapWithDefault(
                                            multiCatSeries.pointLabelSeriesNameToObjectMap(), Objects::toString));
                                    clientSeries.setPointSize(doubleMapWithDefault(
                                            multiCatSeries.pointSizeSeriesNameToNumberMap(),
                                            number -> number == null ? null : number.doubleValue()));

                                    clientSeries.setPointShape(stringMapWithDefault(mergeShapes(
                                            multiCatSeries.pointShapeSeriesNameToStringMap(),
                                            multiCatSeries.pointShapeSeriesNameToShapeMap())));
                                } else if (partitionedTableMultiSeries instanceof MultiXYErrorBarSeries) {
                                    MultiXYErrorBarSeries multiXYErrorBarSeries =
                                            (MultiXYErrorBarSeries) partitionedTableMultiSeries;

                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiXYErrorBarSeries.getX(), SourceType.X, xAxis));
                                    if (multiXYErrorBarSeries.getDrawXError()) {
                                        clientAxes.add(makePartitionedTableSourceDescriptor(
                                                plotHandle, multiXYErrorBarSeries.getXLow(), SourceType.X_LOW, xAxis));
                                        clientAxes.add(makePartitionedTableSourceDescriptor(
                                                plotHandle, multiXYErrorBarSeries.getXHigh(), SourceType.X_HIGH,
                                                xAxis));
                                    }

                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiXYErrorBarSeries.getY(), SourceType.Y, yAxis));
                                    if (multiXYErrorBarSeries.getDrawYError()) {
                                        clientAxes.add(makePartitionedTableSourceDescriptor(
                                                plotHandle, multiXYErrorBarSeries.getYLow(), SourceType.Y_LOW, yAxis));
                                        clientAxes.add(makePartitionedTableSourceDescriptor(
                                                plotHandle, multiXYErrorBarSeries.getYHigh(), SourceType.Y_HIGH,
                                                yAxis));
                                    }

                                    clientSeries.setLineColor(stringMapWithDefault(mergeColors(
                                            multiXYErrorBarSeries.lineColorSeriesNameTointMap(),
                                            multiXYErrorBarSeries.lineColorSeriesNameToStringMap(),
                                            multiXYErrorBarSeries.lineColorSeriesNameToPaintMap())));
                                    clientSeries.setPointColor(stringMapWithDefault(mergeColors(
                                            multiXYErrorBarSeries.pointColorSeriesNameTointMap(),
                                            multiXYErrorBarSeries.pointColorSeriesNameToStringMap(),
                                            multiXYErrorBarSeries.pointColorSeriesNameToPaintMap())));
                                    clientSeries.setLinesVisible(
                                            boolMapWithDefault(
                                                    multiXYErrorBarSeries.linesVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setPointsVisible(
                                            boolMapWithDefault(
                                                    multiXYErrorBarSeries.pointsVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setGradientVisible(
                                            boolMapWithDefault(
                                                    multiXYErrorBarSeries.gradientVisibleSeriesNameTobooleanMap()));
                                    clientSeries.setPointLabelFormat(stringMapWithDefault(
                                            multiXYErrorBarSeries.pointLabelFormatSeriesNameToStringMap()));
                                    clientSeries.setXToolTipPattern(
                                            stringMapWithDefault(
                                                    multiXYErrorBarSeries.xToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setYToolTipPattern(
                                            stringMapWithDefault(
                                                    multiXYErrorBarSeries.yToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setPointLabel(stringMapWithDefault(
                                            multiXYErrorBarSeries.pointLabelSeriesNameToObjectMap(),
                                            Objects::toString));
                                    clientSeries.setPointSize(doubleMapWithDefault(
                                            multiXYErrorBarSeries.pointSizeSeriesNameToNumberMap(),
                                            number -> number == null ? null : number.doubleValue()));

                                    clientSeries.setPointShape(stringMapWithDefault(mergeShapes(
                                            multiXYErrorBarSeries.pointShapeSeriesNameToStringMap(),
                                            multiXYErrorBarSeries.pointShapeSeriesNameToShapeMap())));
                                } else if (partitionedTableMultiSeries instanceof MultiOHLCSeries) {
                                    MultiOHLCSeries multiOHLCSeries =
                                            (MultiOHLCSeries) partitionedTableMultiSeries;

                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiOHLCSeries.getTimeCol(), SourceType.TIME, xAxis));
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiOHLCSeries.getOpenCol(), SourceType.OPEN, yAxis));
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiOHLCSeries.getCloseCol(), SourceType.CLOSE, yAxis));
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiOHLCSeries.getHighCol(), SourceType.HIGH, yAxis));
                                    clientAxes.add(makePartitionedTableSourceDescriptor(
                                            plotHandle, multiOHLCSeries.getLowCol(), SourceType.LOW, yAxis));

                                    clientSeries.setLineColor(stringMapWithDefault(mergeColors(
                                            multiOHLCSeries.lineColorSeriesNameTointMap(),
                                            multiOHLCSeries.lineColorSeriesNameToStringMap(),
                                            multiOHLCSeries.lineColorSeriesNameToPaintMap())));
                                    clientSeries.setPointColor(stringMapWithDefault(mergeColors(
                                            multiOHLCSeries.pointColorSeriesNameTointMap(),
                                            multiOHLCSeries.pointColorSeriesNameToStringMap(),
                                            multiOHLCSeries.pointColorSeriesNameToPaintMap())));
                                    clientSeries.setLinesVisible(
                                            boolMapWithDefault(
                                                    multiOHLCSeries.linesVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setPointsVisible(
                                            boolMapWithDefault(
                                                    multiOHLCSeries.pointsVisibleSeriesNameToBooleanMap()));
                                    clientSeries.setGradientVisible(
                                            boolMapWithDefault(
                                                    multiOHLCSeries.gradientVisibleSeriesNameTobooleanMap()));
                                    clientSeries.setPointLabelFormat(stringMapWithDefault(
                                            multiOHLCSeries.pointLabelFormatSeriesNameToStringMap()));
                                    clientSeries.setXToolTipPattern(
                                            stringMapWithDefault(
                                                    multiOHLCSeries.xToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setYToolTipPattern(
                                            stringMapWithDefault(
                                                    multiOHLCSeries.yToolTipPatternSeriesNameToStringMap()));
                                    clientSeries.setPointLabel(stringMapWithDefault(
                                            multiOHLCSeries.pointLabelSeriesNameToObjectMap(),
                                            Objects::toString));
                                    clientSeries.setPointSize(doubleMapWithDefault(
                                            multiOHLCSeries.pointSizeSeriesNameToNumberMap(),
                                            number -> number == null ? null : number.doubleValue()));

                                    clientSeries.setPointShape(stringMapWithDefault(mergeShapes(
                                            multiOHLCSeries.pointShapeSeriesNameToStringMap(),
                                            multiOHLCSeries.pointShapeSeriesNameToShapeMap())));
                                } else {
                                    errorList.add(
                                            "OpenAPI presently does not support series of type "
                                                    + partitionedTableMultiSeries.getClass());
                                }
                            } else {
                                errorList.add(
                                        "OpenAPI presently does not support series of type " + multiSeries.getClass());
                            }

                            clientSeries.addAllDataSources(clientAxes.build().collect(Collectors.toList()));

                            clientMultiSeriesCollection.add(clientSeries.build());
                        } else {
                            errorList.add(
                                    "OpenAPI presently does not support series of type " + seriesInternal.getClass());
                        }
                    });
        });

        clientChart.addAllSeries(clientSeriesCollection.build().collect(Collectors.toList()));
        clientChart.addAllMultiSeries(clientMultiSeriesCollection.build().collect(Collectors.toList()));

        clientChart.setChartType(FigureDescriptor.ChartDescriptor.ChartType.valueOf(chart.getChartType().name()));
        clientChart.setColspan(chart.colSpan());
        clientChart.setColumn(chart.column());
        assignOptionalField(toCssColorString(chart.getLegendColor()), clientChart::setLegendColor,
                clientChart::clearLegendColor);
        assignOptionalField(toCssFont(chart.getLegendFont()), clientChart::setLegendFont, clientChart::clearLegendFont);
        clientChart.setRow(chart.row());
        clientChart.setRowspan(chart.rowSpan());
        clientChart.setShowLegend(chart.isShowLegend());
        assignOptionalField(chart.getTitle(), clientChart::setTitle, clientChart::clearTitle);
        assignOptionalField(toCssColorString(chart.getTitleColor()), clientChart::setTitleColor,
                clientChart::clearTitleColor);
        assignOptionalField(toCssFont(chart.getTitleFont()), clientChart::setTitleFont, clientChart::clearTitleFont);

        return clientChart.build();
    }

    @NotNull
    private BusinessCalendarDescriptor translateBusinessCalendar(AxisTransformBusinessCalendar axisTransform) {
        final BusinessCalendar businessCalendar = axisTransform.getBusinessCalendar();
        final BusinessCalendarDescriptor.Builder businessCalendarDescriptor = BusinessCalendarDescriptor.newBuilder();
        businessCalendarDescriptor.setName(businessCalendar.name());
        businessCalendarDescriptor.setTimeZone(businessCalendar.timeZone().getId());
        Arrays.stream(BusinessCalendarDescriptor.DayOfWeek.values()).filter(dayOfWeek -> {
            if (dayOfWeek == BusinessCalendarDescriptor.DayOfWeek.UNRECOGNIZED) {
                return false;
            }
            final DayOfWeek day = DayOfWeek.valueOf(dayOfWeek.name());
            return businessCalendar.isBusinessDay(day);
        }).forEach(businessCalendarDescriptor::addBusinessDays);
        businessCalendar.standardBusinessDay().businessTimeRanges().stream().map(period -> {
            // noinspection ConstantConditions
            final String open = TIME_FORMATTER.withZone(businessCalendar.timeZone())
                    .format(period.start());
            // noinspection ConstantConditions
            final String close = TIME_FORMATTER.withZone(businessCalendar.timeZone())
                    .format(period.end());
            final BusinessPeriod.Builder businessPeriod = BusinessPeriod.newBuilder();
            businessPeriod.setOpen(open);
            businessPeriod.setClose(close);
            return businessPeriod;
        }).forEach(businessCalendarDescriptor::addBusinessPeriods);

        businessCalendar.holidays().entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey))
                .map(entry -> {
                    final LocalDate.Builder localDate = LocalDate.newBuilder();
                    localDate.setYear(entry.getKey().getYear());
                    localDate.setMonth(entry.getKey().getMonthValue());
                    localDate.setDay(entry.getKey().getDayOfMonth());
                    final Holiday.Builder holiday = Holiday.newBuilder();
                    entry.getValue().businessTimeRanges().stream().map(bp -> {
                        // noinspection ConstantConditions
                        final String open = TIME_FORMATTER.withZone(businessCalendar.timeZone())
                                .format(bp.start());
                        // noinspection ConstantConditions
                        final String close = TIME_FORMATTER.withZone(businessCalendar.timeZone())
                                .format(bp.end());
                        final BusinessPeriod.Builder businessPeriod = BusinessPeriod.newBuilder();
                        businessPeriod.setOpen(open);
                        businessPeriod.setClose(close);
                        return businessPeriod;
                    }).forEach(holiday::addBusinessPeriods);
                    holiday.setDate(localDate);
                    return holiday.build();
                }).forEach(businessCalendarDescriptor::addHolidays);

        return businessCalendarDescriptor.build();
    }

    private PlotUtils.HashMapWithDefault mergeShapes(
            PlotUtils.HashMapWithDefault strings, PlotUtils.HashMapWithDefault shapes) {
        PlotUtils.HashMapWithDefault result = Stream.of(strings.keySet(), shapes.keySet())
                .flatMap(Set::stream)
                .distinct()
                .collect(Collectors.toMap(
                        Comparable::toString,
                        key -> Objects.requireNonNull(
                                mergeShape(
                                        strings.get(key),
                                        shapes.get(key)),
                                "key " + key + " had nulls in both shape maps"),
                        (s, s2) -> {
                            if (!s.equals(s2)) {
                                throw new IllegalStateException(
                                        "More than one value possible for a given key: " + s + " and " + s2);
                            }
                            return s;
                        },
                        PlotUtils.HashMapWithDefault::new));
        result.setDefault(mergeShape(strings.getDefault(), shapes.getDefault()));
        return result;
    }

    private String mergeShape(String string, Shape shape) {
        if (string != null) {
            return string;
        }
        NamedShape named = null;
        if (shape instanceof NamedShape) {
            named = (NamedShape) shape;
        } else if (shape instanceof JShapes) {
            named = JShapes.shape((JShapes) shape);
        }

        if (named == null) {
            return null;
        }
        return named.name();
    }

    /**
     * Merges the three maps into one, using CSS color strings, including default values
     */
    private PlotUtils.HashMapWithDefault mergeColors(
            PlotUtils.HashMapWithDefault seriesNameTointMap,
            PlotUtils.HashMapWithDefault seriesNameToStringMap,
            PlotUtils.HashMapWithDefault seriesNameToPaintMap) {
        PlotUtils.HashMapWithDefault result =
                Stream.of(seriesNameTointMap.keySet(), seriesNameToStringMap.keySet(), seriesNameToPaintMap.keySet())
                        .flatMap(Set::stream)
                        .distinct()
                        .collect(Collectors.toMap(
                                Comparable::toString,
                                key -> Objects.requireNonNull(
                                        mergeCssColor(
                                                seriesNameTointMap.get(key),
                                                seriesNameToStringMap.get(key),
                                                seriesNameToPaintMap.get(key)),
                                        "key " + key + " had nulls in all three color maps"),
                                (s, s2) -> {
                                    if (!s.equals(s2)) {
                                        throw new IllegalStateException(
                                                "More than one value possible for a given key: " + s + " and " + s2);
                                    }
                                    return s;
                                },
                                PlotUtils.HashMapWithDefault::new));
        // if a "higher precedence" map has a default, it overrides the other defaults
        result.setDefault(mergeCssColor(seriesNameTointMap.getDefault(), seriesNameToStringMap.getDefault(),
                seriesNameToPaintMap.getDefault()));
        return result;
    }

    private String mergeCssColor(Integer intColor, String strColor, io.deephaven.gui.color.Paint paintColor) {
        if (paintColor != null) {
            String candidate = toCssColorString(paintColor);
            if (candidate != null) {
                return candidate;
            } // otherwise failed to be translated, lets at least try the others
        }
        if (strColor != null) {
            // lean on Color's translation. We know toCssColorString won't fail us here, since we're explicitly passing
            // in a Color
            return toCssColorString(new io.deephaven.gui.color.Color(strColor));
        }
        if (intColor != null) {
            return toCssColorString(PlotUtils.intToColor(null, intColor));
        }
        return null;
    }

    private StringMapWithDefault stringMapWithDefault(
            PlotUtils.HashMapWithDefault, String> map) {
        return stringMapWithDefault(map, Function.identity());
    }

    private  StringMapWithDefault stringMapWithDefault(PlotUtils.HashMapWithDefault, T> map,
            Function mappingFunc) {
        StringMapWithDefault.Builder result = StringMapWithDefault.newBuilder();
        String defaultString = mappingFunc.apply(map.getDefault());
        if (defaultString != null) {
            result.setDefaultString(defaultString);
        }
        LinkedHashMap, T> ordered = new LinkedHashMap<>(map);
        result.addAllKeys(ordered.keySet().stream().map(Comparable::toString).collect(Collectors.toList()));
        result.addAllValues(ordered.values().stream().map(mappingFunc).collect(Collectors.toList()));
        return result.build();
    }

    private DoubleMapWithDefault doubleMapWithDefault(
            PlotUtils.HashMapWithDefault, Double> map) {
        return doubleMapWithDefault(map, Function.identity());
    }

    private  DoubleMapWithDefault doubleMapWithDefault(PlotUtils.HashMapWithDefault, T> map,
            Function mappingFunc) {
        DoubleMapWithDefault.Builder result = DoubleMapWithDefault.newBuilder();
        Double defaultDouble = mappingFunc.apply(map.getDefault());
        if (defaultDouble != null) {
            result.setDefaultDouble(defaultDouble);
        }
        LinkedHashMap, T> ordered = new LinkedHashMap<>(map);
        result.addAllKeys(ordered.keySet().stream().map(Comparable::toString).collect(Collectors.toList()));
        result.addAllValues(ordered.values().stream().map(mappingFunc).collect(Collectors.toList()));
        return result.build();
    }

    private BoolMapWithDefault boolMapWithDefault(PlotUtils.HashMapWithDefault, Boolean> map) {
        BoolMapWithDefault.Builder result = BoolMapWithDefault.newBuilder();
        LinkedHashMap, Boolean> ordered = new LinkedHashMap<>(map);
        Boolean defaultBoolean = map.getDefault();
        if (defaultBoolean != null) {
            result.setDefaultBool(defaultBoolean);
        }
        result.addAllKeys(ordered.keySet().stream().map(Comparable::toString).collect(Collectors.toList()));
        result.addAllValues(new ArrayList<>(ordered.values()));
        return result.build();
    }

    private MultiSeriesSourceDescriptor makePartitionedTableSourceDescriptor(PartitionedTableHandle plotHandle,
            String columnName,
            SourceType sourceType, AxisDescriptor axis) {
        MultiSeriesSourceDescriptor.Builder source = MultiSeriesSourceDescriptor.newBuilder();
        source.setAxisId(axis.getId());
        source.setType(sourceType);
        source.setPartitionedTableId(partitionedTablePositionMap.get(plotHandle));
        source.setColumnName(columnName);
        return source.build();
    }

    private SourceDescriptor makeSourceDescriptor(TableHandle tableHandle, String columnName, SourceType sourceType,
            AxisDescriptor axis) {
        SourceDescriptor.Builder source = SourceDescriptor.newBuilder();

        source.setColumnName(columnName);
        source.setTableId(tablePositionMap.get(tableHandle));
        source.setPartitionedTableId(-1);
        source.setAxisId(axis == null ? "-1" : axis.getId());
        source.setType(sourceType);

        return source.build();
    }

    private SourceDescriptor makeSourceDescriptor(SwappableTable swappableTable, String columnName,
            SourceType sourceType, AxisDescriptor axis) {
        SourceDescriptor.Builder source = SourceDescriptor.newBuilder();

        source.setAxisId(axis.getId());
        source.setType(sourceType);
        source.setTableId(-1);

        if (swappableTable instanceof SwappableTableOneClickAbstract) {
            SwappableTableOneClickAbstract oneClick = (SwappableTableOneClickAbstract) swappableTable;
            source.setColumnName(columnName);
            source.setColumnType(
                    swappableTable.getTableDefinition().getColumn(columnName).getDataType().getCanonicalName());
            source.setPartitionedTableId(partitionedTablePositionMap.get(oneClick.getPartitionedTableHandle()));
            source.setOneClick(makeOneClick(oneClick));

        } else {
            errorList.add("OpenAPI does not presently support swappable table of type " + swappableTable.getClass());
        }

        return source.build();
    }

    private SourceDescriptor makeSourceDescriptor(IndexableNumericData data, SourceType sourceType,
            AxisDescriptor axis) {
        SourceDescriptor.Builder source = SourceDescriptor.newBuilder();
        source.setAxisId(axis.getId());
        source.setType(sourceType);
        if (data instanceof IndexableNumericDataTable) {
            ColumnHandlerFactory.ColumnHandler columnHandler = ((IndexableNumericDataTable) data).getColumnHandler();

            source.setColumnName(columnHandler.getColumnName());
            source.setTableId(tablePositionMap.get(columnHandler.getTableHandle()));
            source.setPartitionedTableId(-1);
        } else if (data instanceof IndexableNumericDataSwappableTable) {
            IndexableNumericDataSwappableTable swappableTable = (IndexableNumericDataSwappableTable) data;
            if (swappableTable.getSwappableTable() instanceof SwappableTableOneClickAbstract) {
                SwappableTableOneClickAbstract oneClick =
                        (SwappableTableOneClickAbstract) swappableTable.getSwappableTable();
                if (oneClick instanceof SwappableTableOneClickPartitioned
                        && ((SwappableTableOneClickPartitioned) oneClick).getTransform() != null) {
                    errorList.add(
                            "OpenAPI does not presently support swappable tables that also use transform functions");
                    return source.build();
                }
                source.setColumnName(swappableTable.getColumn());
                source.setColumnType(swappableTable.getSwappableTable().getTableDefinition()
                        .getColumn(swappableTable.getColumn()).getDataType().getCanonicalName());
                source.setTableId(-1);
                source.setPartitionedTableId(partitionedTablePositionMap.get(oneClick.getPartitionedTableHandle()));
                source.setOneClick(makeOneClick(oneClick));
            } else {
                errorList.add("OpenAPI does not presently support swappable table of type "
                        + swappableTable.getSwappableTable().getClass());
            }

        } else {
            // TODO read out the array for constant data
            errorList.add("OpenAPI does not presently support source of type " + data.getClass());
        }
        return source.build();
    }

    private OneClickDescriptor makeOneClick(SwappableTableOneClickAbstract swappableTable) {
        OneClickDescriptor.Builder oneClick = OneClickDescriptor.newBuilder();
        oneClick.addAllColumns(swappableTable.getByColumns());
        oneClick.addAllColumnTypes(swappableTable.getByColumns()
                .stream()
                .map(colName -> swappableTable.getPartitionedTableHandle().getTableDefinition()
                        .getColumn(colName).getDataType().getCanonicalName())
                .collect(Collectors.toList()));
        oneClick.setRequireAllFiltersToDisplay(swappableTable.isRequireAllFiltersToDisplay());
        return oneClick.build();
    }

    private String toCssColorString(io.deephaven.gui.color.Paint color) {
        if (color == null) {
            return null;
        }
        if (!(color instanceof io.deephaven.gui.color.Color)) {
            errorList.add("OpenAPI does not presently support paint of type " + color);
            return null;
        }
        Color paint = (Color) color.javaColor();
        return "#" + Integer.toHexString(paint.getRGB()).substring(2);
    }

    private String toCssFont(Font font) {
        if (font == null) {
            return null;
        }
        return font.javaFont().getName();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy