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

com.vaadin.flow.component.spreadsheet.charts.converter.confwriter.ChartDataToVaadinConfigWriter Maven / Gradle / Ivy

There is a newer version: 24.6.0
Show newest version
/**
 * 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.confwriter;

import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.vaadin.flow.component.charts.model.AxisType;
import com.vaadin.flow.component.charts.model.ChartModel;
import com.vaadin.flow.component.charts.model.Configuration;
import com.vaadin.flow.component.charts.model.Frame;
import com.vaadin.flow.component.charts.model.HorizontalAlign;
import com.vaadin.flow.component.charts.model.LayoutDirection;
import com.vaadin.flow.component.charts.model.Legend;
import com.vaadin.flow.component.charts.model.Options3d;
import com.vaadin.flow.component.charts.model.PlotOptionsSeries;
import com.vaadin.flow.component.charts.model.Series;
import com.vaadin.flow.component.charts.model.Title;
import com.vaadin.flow.component.charts.model.VerticalAlign;
import com.vaadin.flow.component.charts.model.XAxis;
import com.vaadin.flow.component.charts.model.YAxis;
import com.vaadin.flow.component.charts.model.style.Color;
import com.vaadin.flow.component.charts.model.style.FontWeight;
import com.vaadin.flow.component.charts.model.style.GradientColor;
import com.vaadin.flow.component.charts.model.style.SolidColor;
import com.vaadin.flow.component.charts.model.style.Style;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.AbstractSeriesData;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.AxisProperties;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.BackgroundProperties;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.BorderStyle;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.ColorProperties;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.GradientProperties;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.TextProperties;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.TitleProperties;
import com.vaadin.flow.component.spreadsheet.charts.converter.chartdata.ChartData.View3dData;

@SuppressWarnings("serial")
public class ChartDataToVaadinConfigWriter {

    private static final int DEFAULT_LEGEND_Y_OFFSET = 30;

    private Logger logger = Logger
            .getLogger(ChartDataToVaadinConfigWriter.class.getSimpleName());
    {
        logger.setLevel(Level.OFF);
    }

    public Configuration createConfigurationFromChartData(
            ChartData definition) {
        logger.info("createConfData()");

        if (definition.plotData.size() > 0) {
            logger.info("*** NEXT CHART *** title: " + definition.title
                    + " , series type: "
                    + definition.plotData.get(0).getClass().getSimpleName());
        }

        Configuration conf = new Configuration();

        if (definition.view3dData != null) {
            logger.info("view3dData: " + definition.view3dData.rotation3dAngleA
                    + "/" + definition.view3dData.rotation3dAngleB);

            conf.getChart().setOptions3d(getOptions3d(definition.view3dData));
        } else {
            logger.info("definition.view3dData is null");
        }

        convertPlotData(definition, conf);

        setDefaults(conf);

        // special handling for turning pie charts into donuts
        PieToDonutConverter.convertIfNeeded(definition, conf);

        conf.setTitle(createTitle(definition.title, definition.titleStyle));

        updateLegendPosition(conf.getLegend(), definition);

        updateLegendTextProperties(conf.getLegend(),
                definition.legendProperties.textProperties);

        updateBorder(conf.getChart(), definition.borderStyle);

        updateBackgroundColor(conf, definition.background);

        updateXAxis(conf.getxAxis(), definition.xAxisProperties);
        updateYAxes(conf, definition.yAxesProperties);

        if (conf.getSeries().isEmpty()) {
            conf.setSubTitle("*** Unsupported chart type ***");
        }
        updateTooltip(definition, conf);

        return conf;
    }

    private void updateTooltip(ChartData definition, Configuration conf) {
        StringBuilder formatter = new StringBuilder();
        formatter.append("function(){");
        formatter.append("var text='';");
        Iterator seriesIt = definition.plotData.iterator();
        int i = 0;
        while (seriesIt.hasNext()) {
            AbstractSeriesData series = seriesIt.next();

            String seriesTitle = "'Series'";
            String pointTitle = "'Point'";
            String pointData;
            if (series.is3d) {
                pointData = "z";
            } else {
                pointData = "y";
            }

            // Excel uses one based numbering
            String seriesName = series.name == null ? Integer.toString(i + 1)
                    : "this.series.name";

            String seriesFormatter = "if(this.series.options.id == '$id'){\n"
                    + "   var formattedNumber;\n"
                    + "   var signlessNumer = Math.abs(this.$v);"
                    + "   if ($tooltipDecimals < 0 ) {\n"
                    + "      //Round numbers to contain only 9 digits if they contain decimals.\n"
                    + "      var tooltipDecimals = (signlessNumer>1.0 ? 9-Math.floor(Math.log(signlessNumer)/Math.LN10) : 9);\n"
                    + "      formattedNumber = (Math.round(this.$v) == this.$v ? this.$v : Math.round(this.$v * Math.pow(10,tooltipDecimals))/Math.pow(10,tooltipDecimals));\n"
                    + "   } else {\n"
                    + "      //numberFormat can handle numbers 20 digits long.\n"
                    + "      var tooltipDecimals = (Math.ceil(Math.log(signlessNumer)/Math.LN10) + $tooltipDecimals<= 20 ? $tooltipDecimals : 20);\n"
                    + "      formattedNumber = Highcharts.numberFormat(this.$v, tooltipDecimals);\n"
                    + "   }\n" + "   text = $seriesTitle + ' ' + \n"
                    + "      (typeof $seriesName == 'number' ? $seriesName : JSON.stringify($seriesName)) + ' ' + $pointTitle + ' ' + \n"
                    + "      (('name' in this.point) ? JSON.stringify(this.point.name) : this.x + 1) + \n"
                    + "      ' 
' + formattedNumber;" + "}"; formatter.append(seriesFormatter.replace("$v", pointData) .replace("$id", Integer.toString(i++)) .replace("$seriesTitle", seriesTitle) .replace("$seriesName", seriesName) .replace("$pointTitle", pointTitle) .replace("$tooltipDecimals", Integer.toString(series.tooltipDecimals))); if (seriesIt.hasNext()) { formatter.append(" else \n"); } } formatter.append("return text; }"); conf.getTooltip().setFormatter(formatter.toString()); } private void updateYAxes(Configuration conf, List yAxisProperties) { if (yAxisProperties == null || yAxisProperties.size() == 0) { return; } YAxis defaultyAxis = conf.getyAxis(); AxisProperties firstYAxisProps = yAxisProperties.get(0); updateYAxisTitle(defaultyAxis, firstYAxisProps); // todo: how to tell if the stored double is really a date? defaultyAxis.setMin(firstYAxisProps.minVal); defaultyAxis.setMax(firstYAxisProps.maxVal); for (AxisProperties axProp : yAxisProperties.subList(1, yAxisProperties.size())) { YAxis axis = new YAxis(); axis.setOpposite(true); conf.addyAxis(axis); updateYAxisTitle(axis, axProp); axis.setMin(axProp.minVal); axis.setMax(axProp.maxVal); } } private void updateBackgroundColor(Configuration conf, BackgroundProperties background) { if (background == null) { return; } if (background.color != null) { conf.getChart().setBackgroundColor( createSolidColorFromColorProperties(background.color, SolidColor.WHITE)); } else if (background.gradient != null) { conf.getChart().setBackgroundColor( createGradientFromGradientProps(background.gradient)); } } private Color createGradientFromGradientProps(GradientProperties prop) { double[] grPts = calculateGradientPoints(prop.angle); GradientColor linear = GradientColor.createLinear(grPts[0], grPts[1], grPts[2], grPts[3]); for (Entry grdStop : prop.colorStops .entrySet()) { Double position = grdStop.getKey(); ColorProperties colorProp = grdStop.getValue(); linear.addColorStop(position, createSolidColorFromColorProperties( colorProp, SolidColor.WHITE)); } return linear; } private double[] calculateGradientPoints(double angle) { // into radians and rotate 180 deg double angleInRad = angle * 2 * Math.PI - Math.PI; double x1 = Math.cos(angleInRad), y1 = Math.sin(angleInRad); // cos and sin map to -1..1, we need to remap to 0..1 x1 = (x1 + 1) / 2; y1 = (y1 + 1) / 2; return new double[] { x1, y1, 1 - x1, 1 - y1 }; } private SolidColor createSolidColorFromColorProperties( ColorProperties colorProp, SolidColor defaultColor) { if (colorProp == null) { return defaultColor; } return new SolidColor(colorProp.red, colorProp.green, colorProp.blue, colorProp.opacity); } private void updateXAxis(XAxis axis, AxisProperties axisProperties) { if (axisProperties == null) { return; } axis.setTitle(wrapStringIntoItalicsTagIfNeeded(axisProperties.title, axisProperties.textProperties)); axis.getTitle().setStyle( createStyleFromTextFroperties(axisProperties.textProperties)); axis.setMin(axisProperties.minVal); axis.setMax(axisProperties.maxVal); } private void updateYAxisTitle(YAxis axis, AxisProperties axisProperties) { if (axisProperties == null) { axis.setTitle((String) null); return; } axis.setTitle(wrapStringIntoItalicsTagIfNeeded(axisProperties.title, axisProperties.textProperties)); axis.getTitle().setStyle( createStyleFromTextFroperties(axisProperties.textProperties)); } private void updateLegendTextProperties(Legend legend, TextProperties textPr) { Style style = createStyleFromTextFroperties(textPr); legend.setItemStyle(style); } private void updateBorder(ChartModel chart, BorderStyle borderStyle) { chart.setBorderRadius(borderStyle.radius); chart.setBorderWidth(borderStyle.width); chart.setBorderColor( createSolidColorFromColorProperties(borderStyle.color, null)); } private Options3d getOptions3d(View3dData view3dData) { logger.info("getOptions3d()"); Options3d options3d = new Options3d(); options3d.setEnabled(true); options3d.setAlpha(view3dData.rotation3dAngleA); options3d.setBeta(view3dData.rotation3dAngleB); options3d.setDepth(100); options3d.setViewDistance(400); Frame frame = new Frame(); options3d.setFrame(frame); return options3d; } private void updateLegendPosition(Legend legend, ChartData definition) { logger.info("updateLegend()"); switch (definition.legendProperties.position) { case NONE: legend.setEnabled(false); break; case RIGHT: legend.setVerticalAlign(VerticalAlign.MIDDLE); legend.setAlign(HorizontalAlign.RIGHT); legend.setLayout(LayoutDirection.VERTICAL); break; case BOTTOM: legend.setVerticalAlign(VerticalAlign.BOTTOM); legend.setAlign(HorizontalAlign.CENTER); legend.setLayout(LayoutDirection.HORIZONTAL); break; case LEFT: legend.setVerticalAlign(VerticalAlign.MIDDLE); legend.setAlign(HorizontalAlign.LEFT); legend.setLayout(LayoutDirection.VERTICAL); break; case TOP: legend.setVerticalAlign(VerticalAlign.TOP); legend.setAlign(HorizontalAlign.CENTER); legend.setLayout(LayoutDirection.HORIZONTAL); // if legend is aligned to top it overlaps the title if there is // one. this does not happen in a jsfiddle test, so maybe one of the // plugins that Vaadin Charts is loading are causing it, requires // further investigation. At this point we try to estimate the // titles' vertical size and move the legend down legend.setY(estimateTitleVerticalSize(definition)); break; case TOP_RIGHT: legend.setVerticalAlign(VerticalAlign.TOP); legend.setAlign(HorizontalAlign.RIGHT); legend.setLayout(LayoutDirection.VERTICAL); break; } } private int estimateTitleVerticalSize(ChartData definition) { if (definition.title == null || definition.title.isEmpty()) { return 0; } else if (definition.titleStyle != null && definition.titleStyle.textProperties != null && definition.titleStyle.textProperties.size > 0) { return (int) definition.titleStyle.textProperties.size; } else { return DEFAULT_LEGEND_Y_OFFSET; } } protected void convertPlotData(ChartData definition, Configuration conf) { logger.info("convertPlotData()"); int i = 0; for (AbstractSeriesData series : definition.plotData) { AbstractSeriesDataWriter seriesDataWriter = series .getSeriesDataWriter(); seriesDataWriter.configureChart(conf); Series chartSeries = seriesDataWriter .convertSeries(definition.blanksAsZeros); chartSeries.setId(Integer.toString(i++)); conf.addSeries(chartSeries); if (series.categories.size() > 0) { conf.getxAxis().setType(AxisType.CATEGORY); } } } protected Title createTitle(String titleString, TitleProperties titleProps) { logger.info("createTitle()"); if (titleString == null) { return new Title(""); } Style style = createStyleFromTextFroperties(titleProps.textProperties); Title title = new Title(wrapStringIntoItalicsTagIfNeeded(titleString, titleProps.textProperties)); title.setFloating(titleProps.isFloating); title.setStyle(style); return title; } private String wrapStringIntoItalicsTagIfNeeded(String string, TextProperties textPr) { if (textPr != null && textPr.italics) { return "" + string + ""; } else { return string; } } private Style createStyleFromTextFroperties(TextProperties textProps) { Style style = new Style(); if (textProps == null) { return style; } style.setColor(createSolidColorFromColorProperties(textProps.color, SolidColor.GREY)); if (textProps.size > 0) { style.setFontSize(textProps.size + "pt"); } if (textProps.fontFamily != null) { style.setFontFamily(textProps.fontFamily); } if (textProps.bold) { style.setFontWeight(FontWeight.BOLD); } // chart model style doesn't support italics return style; } /** * This is supposed to set the defaults closed to what Excel uses. */ protected void setDefaults(Configuration conf) { logger.info("setDefaults()"); // default in Excel conf.getyAxis().setTitle((String) null); conf.addPlotOptions(new PlotOptionsSeries() { { setAnimation(false); setAllowPointSelect(true); } }); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy