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

com.vaadin.flow.component.spreadsheet.charts.converter.xssfreader.AbstractSeriesReader Maven / Gradle / Ivy

/**
 * Copyright 2000-2024 Vaadin Ltd.
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See {@literal } for the full
 * license.
 */
package com.vaadin.flow.component.spreadsheet.charts.converter.xssfreader;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTAxDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTMultiLvlStrRef;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTSerTx;

import com.vaadin.flow.component.spreadsheet.Spreadsheet;
import com.vaadin.flow.component.spreadsheet.Spreadsheet.CellValueChangeEvent;
import com.vaadin.flow.component.spreadsheet.Spreadsheet.CellValueChangeListener;
import com.vaadin.flow.component.spreadsheet.SpreadsheetUtil;
import com.vaadin.flow.component.spreadsheet.charts.converter.Utils;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.AbstractSeriesData;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.AbstractSeriesData.DataSelectListener;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.AbstractSeriesData.SeriesPoint;

public abstract class AbstractSeriesReader {

    private final XmlObject ctChart;
    private final Spreadsheet spreadsheet;
    private final boolean is3d;
    protected final boolean showDataInHiddenCells;

    public enum ValueUpdateMode {
        Y_VALUES, X_VALUES, Z_VALUES, CATEGORIES
    };

    public AbstractSeriesReader(XmlObject ctChart, Spreadsheet spreadsheet,
            boolean showDataInHiddenCells) {
        this(ctChart, spreadsheet, false, showDataInHiddenCells);
    }

    public AbstractSeriesReader(XmlObject ctChart, Spreadsheet spreadsheet,
            boolean is3d, boolean showDataInHiddenCells) {
        this.ctChart = ctChart;
        this.spreadsheet = spreadsheet;
        this.is3d = is3d;
        this.showDataInHiddenCells = showDataInHiddenCells;
    }

    protected abstract SERIES_DATA_TYPE createSeriesDataObject(
            CT_SER_TYPE serie);

    public List getSeries() {
        List list = new ArrayList<>();

        for (CT_SER_TYPE serie : getSerList()) {
            list.add(createAndFillSeriesDataObject(serie));
        }

        return list;
    }

    private SERIES_DATA_TYPE createAndFillSeriesDataObject(CT_SER_TYPE serie) {
        SERIES_DATA_TYPE seriesData = createSeriesDataObject(serie);
        fillSeriesData(seriesData, serie);
        return seriesData;
    }

    protected XmlObject getChart() {
        return ctChart;
    }

    @SuppressWarnings("unchecked")
    private List getSerList() {
        return (List) Utils.callMethodUsingReflection(ctChart,
                "getSerList");
    }

    protected void fillSeriesData(SERIES_DATA_TYPE seriesData,
            CT_SER_TYPE serie) {
        CTSerAdapter ctSerAdapter = new CTSerAdapter(serie);

        seriesData.name = tryGetSeriesName(ctSerAdapter.getTx());
        createCategories(ctSerAdapter.getCat(), seriesData);
        createSeriesDataPoints(ctSerAdapter.getVal(), seriesData);
        seriesData.is3d = this.is3d;
    }

    protected void createCategories(CTAxDataSource axisDataSource,
            SERIES_DATA_TYPE seriesData) {
        if (axisDataSource == null) {
            return;
        }

        final List cellReferences = getCategoryCellReferences(
                axisDataSource);

        // AbstractList is not serializable, so we wrap it into an ArrayList
        seriesData.categories = new ArrayList<>(new AbstractList() {
            @Override
            public String get(int index) {
                return Utils.getStringValue(cellReferences.get(index),
                        getSpreadsheet());
            }

            @Override
            public int size() {
                return cellReferences.size();
            }
        });
        handleReferencedValueUpdates(cellReferences, seriesData,
                ValueUpdateMode.CATEGORIES);
    }

    private List getCategoryCellReferences(
            CTAxDataSource axisDataSource) {

        if (axisDataSource.isSetStrRef()) {
            String formula = axisDataSource.getStrRef().getF();
            return Utils.getAllReferencedCells(formula, spreadsheet,
                    showDataInHiddenCells);
        } else if (axisDataSource.isSetNumRef()) {
            String formula = axisDataSource.getNumRef().getF();
            return Utils.getAllReferencedCells(formula, spreadsheet,
                    showDataInHiddenCells);
        } else if (axisDataSource.isSetMultiLvlStrRef()) {
            return tryHandleMultilevelCategories(axisDataSource);
        } else {
            // others not supported yet
            return Collections.emptyList();
        }
    }

    private List tryHandleMultilevelCategories(
            CTAxDataSource axisDataSource) {
        // HighChart doesn't support multilevel, take only the first one
        final CTMultiLvlStrRef multiLvlStrRef = axisDataSource
                .getMultiLvlStrRef();

        String formula = multiLvlStrRef.getF();

        final List allReferencedCells = Utils
                .getAllReferencedCells(formula, spreadsheet,
                        showDataInHiddenCells);

        if (!multiLvlStrRef.getMultiLvlStrCache().isSetPtCount()) {
            return allReferencedCells;
        } else {
            return getCategoryCellsFromMultilevelReferences(multiLvlStrRef,
                    allReferencedCells);
        }
    }

    /**
     * This method tries to calculate the last level of categories from all
     * multilevel category cells and cached values.
     */
    private List getCategoryCellsFromMultilevelReferences(
            CTMultiLvlStrRef multiLvlStrRef,
            final List allReferencedCells) {
        final CellReference firstCell = allReferencedCells.get(0);
        final CellReference lastCell = allReferencedCells
                .get(allReferencedCells.size() - 1);

        final int width = lastCell.getCol() - firstCell.getCol() + 1;
        final int height = lastCell.getRow() - firstCell.getRow() + 1;

        final int numOfPointsInCache = (int) multiLvlStrRef
                .getMultiLvlStrCache().getPtCount().getVal();
        final int numOfLevels = allReferencedCells.size() / numOfPointsInCache;

        if (numOfLevels == width) {
            return new AbstractList() {
                @Override
                public CellReference get(int index) {
                    return allReferencedCells
                            .get((numOfLevels - 1) + index * numOfLevels);
                }

                @Override
                public int size() {
                    return numOfPointsInCache;
                }
            };
        } else if (numOfLevels == height) {
            return new AbstractList() {
                @Override
                public CellReference get(int index) {
                    return allReferencedCells
                            .get(allReferencedCells.size() - width + index);
                }

                @Override
                public int size() {
                    return numOfPointsInCache;
                }
            };
        } else {
            System.err.println("Could not handle multilevel categories");
            return Collections.emptyList();
        }
    }

    protected void createSeriesDataPoints(CTNumDataSource val,
            SERIES_DATA_TYPE seriesData) {
        final String formula = val.getNumRef().getF();

        final List ptList = Utils.getAllReferencedCells(formula,
                spreadsheet, showDataInHiddenCells);

        List list = new ArrayList<>();

        for (int i = 0; i < ptList.size(); i++) {
            Double cellNumericValue = getNumericValueFromCellRef(ptList.get(i));
            list.add(new SeriesPoint(i, cellNumericValue));
        }

        seriesData.seriesData = list;
        seriesData.tooltipDecimals = calculateDecimalsForTooltip(ptList);

        handleReferencedValueUpdates(ptList, seriesData,
                ValueUpdateMode.Y_VALUES);

        seriesData.dataSelectListener = new DataSelectListener() {
            @Override
            public void dataSelected() {
                AreaReference[] areaReferences = Utils.getAreaReferences(
                        getSpreadsheet().getWorkbook().getSpreadsheetVersion(),
                        formula);

                getSpreadsheet().setSelectionRange(
                        areaReferences[0].getFirstCell().getRow(),
                        areaReferences[0].getFirstCell().getCol(),
                        areaReferences[areaReferences.length - 1].getLastCell()
                                .getRow(),
                        areaReferences[areaReferences.length - 1].getLastCell()
                                .getCol());
            }
        };
    }

    private int calculateDecimalsForTooltip(List ptList) {

        if (ptList.size() <= 0) {
            // No points, so go with the default number of decimals
            return -1;
        }

        CellReference ref = ptList.get(0);
        Sheet sheet = spreadsheet.getWorkbook().getSheet(ref.getSheetName());
        Cell cell = spreadsheet.getCell(ref, sheet);
        if (cell == null) {
            return -1;
        }
        CellStyle style = cell.getCellStyle();
        String styleString = style.getDataFormatString();
        if (styleString == null || styleString.isEmpty()
                || styleString.equals("General")) {
            // No formatting info given, so go with the default number of
            // decimals
            return -1;
        }

        // In formatting strings "." is always used it seems.
        char sep = '.';

        // Take the last occurrence if the user has the same symbol as thousand
        // separator (should not be possible)
        int sepIndex = styleString.trim().lastIndexOf(sep);
        int decimalCount;
        if (sepIndex < 0) {
            decimalCount = 0;
        } else {
            decimalCount = styleString.length() - sepIndex - 1;
        }
        return decimalCount;
    }

    void onValueChange(final List referencedCells,
            final SERIES_DATA_TYPE seriesData, final ValueUpdateMode updateMode,
            Spreadsheet.ValueChangeEvent event) {
        if (seriesData.dataUpdateListener == null) {
            return;
        }

        for (CellReference changedCell : event.getChangedCells()) {
            // getChangedCell erroneously provides relative cell refs
            // if this gets fixed, this conversion method should be
            // removed
            // https://dev.vaadin.com/ticket/19717
            updatePoint(referencedCells, seriesData, updateMode, changedCell);
        }
    }

    @SuppressWarnings("serial")
    protected void handleReferencedValueUpdates(
            final List referencedCells,
            final SERIES_DATA_TYPE seriesData,
            final ValueUpdateMode updateMode) {

        spreadsheet.addCellValueChangeListener(new CellValueChangeListener() {
            @Override
            public void onCellValueChange(CellValueChangeEvent event) {
                onValueChange(referencedCells, seriesData, updateMode, event);
            }
        });

        spreadsheet.addFormulaValueChangeListener(
                new Spreadsheet.FormulaValueChangeListener() {
                    @Override
                    public void onFormulaValueChange(
                            Spreadsheet.FormulaValueChangeEvent event) {
                        onValueChange(referencedCells, seriesData, updateMode,
                                event);
                    }
                });
    }

    private void updatePoint(List referencedCells,
            SERIES_DATA_TYPE seriesData, ValueUpdateMode updateMode,
            CellReference changedCell) {
        CellReference absoluteChangedCell = SpreadsheetUtil
                .relativeToAbsolute(spreadsheet, changedCell);
        if (!referencedCells.contains(absoluteChangedCell)) {
            return;
        }

        final int index = referencedCells.indexOf(absoluteChangedCell);

        if (updateMode != ValueUpdateMode.CATEGORIES) {
            final SeriesPoint item = seriesData.seriesData.get(index);
            final Double cellValue = Utils.getNumericValue(absoluteChangedCell,
                    spreadsheet);
            if (updateMode == ValueUpdateMode.X_VALUES) {
                item.xValue = cellValue;
                seriesData.dataUpdateListener.xDataModified(index, cellValue);
            }
            if (updateMode == ValueUpdateMode.Y_VALUES) {
                item.yValue = cellValue;
                seriesData.dataUpdateListener.yDataModified(index, cellValue);
            }
            if (updateMode == ValueUpdateMode.Z_VALUES) {
                item.zValue = cellValue;
                seriesData.dataUpdateListener.zDataModified(index, cellValue);
            }
        } else {
            final String cellValue = Utils.getStringValue(absoluteChangedCell,
                    spreadsheet);
            seriesData.dataUpdateListener.categoryModified(index, cellValue);
        }
    }

    protected String tryGetSeriesName(CTSerTx tx) {
        try {
            if (tx.isSetV()) {
                return tx.getV();
            }

            if (tx.isSetStrRef()) {
                String formula = tx.getStrRef().getF();

                return Utils.getStringValueFromFormula(formula, spreadsheet);
            }
        } catch (Exception e) {
        }
        return null;
    }

    protected Spreadsheet getSpreadsheet() {
        return spreadsheet;
    }

    protected Double getNumericValueFromCellRef(CellReference cellRef) {
        return Utils.getNumericValue(cellRef, getSpreadsheet());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy