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

com.sibvisions.rad.ui.vaadin.impl.component.chart.VaadinChartJs Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 SIB Visions GmbH
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 *
 * History
 *
 * 08.09.2021 - [DJ] - creation 
 */
package com.sibvisions.rad.ui.vaadin.impl.component.chart;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;

import jvx.rad.model.IDataBook;
import jvx.rad.model.IDataPage;
import jvx.rad.model.IDataRow;
import jvx.rad.model.IRowDefinition;
import jvx.rad.model.ModelException;
import jvx.rad.model.datatype.BigDecimalDataType;
import jvx.rad.model.datatype.IDataType;
import jvx.rad.model.datatype.LongDataType;
import jvx.rad.model.datatype.StringDataType;
import jvx.rad.model.datatype.TimestampDataType;
import jvx.rad.model.event.DataBookEvent;
import jvx.rad.model.event.DataRowEvent;
import jvx.rad.model.event.IDataBookListener;
import jvx.rad.model.event.IDataRowListener;
import jvx.rad.ui.control.IChart;
import jvx.rad.ui.event.UIMouseEvent;
import jvx.rad.util.ExceptionHandler;
import jvx.rad.util.TranslationMap;

import com.byteowls.vaadin.chartjs.ChartJs;
import com.byteowls.vaadin.chartjs.ChartJs.DataPointClickListener;
import com.byteowls.vaadin.chartjs.config.BarChartConfig;
import com.byteowls.vaadin.chartjs.config.ChartConfig;
import com.byteowls.vaadin.chartjs.config.DonutChartConfig;
import com.byteowls.vaadin.chartjs.config.LineChartConfig;
import com.byteowls.vaadin.chartjs.config.PieChartConfig;
import com.byteowls.vaadin.chartjs.data.BarDataset;
import com.byteowls.vaadin.chartjs.data.Data;
import com.byteowls.vaadin.chartjs.data.LineDataset;
import com.byteowls.vaadin.chartjs.data.PieDataset;
import com.byteowls.vaadin.chartjs.data.PointStyle;
import com.byteowls.vaadin.chartjs.data.ScatterDataset;
import com.byteowls.vaadin.chartjs.data.TimeLineDataset;
import com.byteowls.vaadin.chartjs.options.Position;
import com.byteowls.vaadin.chartjs.options.scale.Axis;
import com.byteowls.vaadin.chartjs.options.scale.BaseScale;
import com.byteowls.vaadin.chartjs.options.scale.CategoryScale;
import com.byteowls.vaadin.chartjs.options.scale.LinearScale;
import com.byteowls.vaadin.chartjs.options.scale.LinearTicks;
import com.byteowls.vaadin.chartjs.options.scale.TimeScale;
import com.sibvisions.rad.ui.vaadin.impl.VaadinComponent;
import com.sibvisions.util.ArrayUtil;
import com.sibvisions.util.log.ILogger;
import com.sibvisions.util.log.LoggerFactory;
import com.sibvisions.util.type.NumberUtil;
import com.vaadin.server.ClientConnector.AttachEvent;
import com.vaadin.server.ClientConnector.AttachListener;
import com.vaadin.server.ClientConnector.DetachEvent;
import com.vaadin.server.ClientConnector.DetachListener;
import com.vaadin.server.Sizeable.Unit;

/**
 * The VaadinChartJs is the IChart
 * implementation for vaadin, it uses older version of Chart.js library(2.7.2).
 * 
 * @author Jozef Dorko
 */
@Deprecated
public class VaadinChartJs extends VaadinComponent
                           implements IChart,
                                      Runnable,
                                      IDataRowListener,
                                      IDataBookListener,
                                      DataPointClickListener
{

    /** Transparent colors. */
    public static final String[] TRANSPARENT_COLORS = new String[] {
            "rgba(255, 99, 132, 0.7)",
            "rgba(54, 162, 235, 0.7)",
            "rgba(255, 206, 86, 0.7)",
            "rgba(75, 192, 192, 0.7)",
            "rgba(153, 102, 255, 0.7)",
            "rgba(255, 159, 64, 0.7)"
    };
    
    /** Colors. */
    public static final String[] COLORS = new String[] {
            "rgba(255, 99, 132)",
            "rgba(54, 162, 235)",
            "rgba(255, 206, 86)",
            "rgba(75, 192, 192)",
            "rgba(153, 102, 255)",
            "rgba(255, 159, 64)"
    };
    
    /** The point styles. */
    public static final String[] POINT_STYLES = new String[] {
            "rect",
            "circle",
            "triangle",
            "star",
            "cross",
            "dash",
            "rectRot",
            "crossRot",
            "line",
            "rectRounded"
    };
    
    /** The sum all key. */
    private static final String  SUM_ALL_KEY             = "$sumALLtutti$?#.7(";

    /** The logger. */
    private static ILogger       logger                  = LoggerFactory.getInstance(VaadinChartJs.class);

    /** The DataBook to be shown. */
    private IDataBook            dataBook                = null;

    /** The current selected data page. **/
    private IDataPage            currentDataPage         = null;
    
    /** The x column name. */
    private String               sXColumnName            = null;

    /** The chart title. */
    private String               sTitle                  = null;

    /** The x axis title. */
    private String               sXTitle                 = null;

    /** The y axis title. */
    private String               sYTitle                 = null;

    /** The chart style like bar, line, area. **/
    private int                  chartStyle              = IChart.STYLE_LINES;

    /** The y column names. */
    private String[]             saYColumnNames          = null;

    /** The Configuration for the vaadin js chart. **/
    private ChartConfig          config;

    /** The translation mapping. */
    private TranslationMap       tmpTranslation          = null;

    /** whether the chart is attached. */
    private boolean              bAttached               = false;

    /** Tells, if notifyRepaint is called the first time. */
    private boolean              bFirstNotifyRepaintCall = true;

    /** Ignore repaint indicator. */
    private boolean              bIgnoreRepaint          = true;

    /** whether the translation is enabled. */
    private boolean              bTranslationEnabled     = true;
    
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Initialization
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    /**
     * Creates a new instance of VaadinChartJs.
     */
    public VaadinChartJs()
    {
        super(new ChartJs());

        resource.addClickListener(this);
        
        resource.addAttachListener(new AttachListener()
        {
            
            @Override
            public void attach(AttachEvent event)
            {
                bAttached = true;
                
                installChart();
            }
        });
        
        resource.addDetachListener(new DetachListener()
        {
            
            @Override
            public void detach(DetachEvent event)
            {
                uninstallChart();
                
                bAttached = false;
            }
        });
    }
    
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Interface implementation
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    /**
     * The run method is invoked from AWT EventQueue. 
     * It enables events from the model again. 
     * Due to performance reasons the events are disabled from the first call of
     * notifyRepaint until the EventQueue calls the run method. 
     * This minimizes the repaints of the control. 
     */
    public void run()
    {
        try
        {
            cancelEditing();        
            
            Lock lockInstance = null;
            
            try
            {
                lockInstance = resource.getUI().getSession().getLockInstance();
            }
            catch (Exception e)
            {
                // Do nothing.
            }
            
            if (lockInstance != null)
            {
                lockInstance.lock();
            }
            
            try 
            {
                installChart();
                initializeData();
            }
            catch (Exception e)
            {
                ExceptionHandler.show(e);
            }
            finally 
            {
                if (lockInstance != null)
                {
                    lockInstance.unlock();
                }
            }
        }
        finally
        {
            bFirstNotifyRepaintCall = true;
        }
    }

    /**
     * {@inheritDoc}
     */
    public IDataBook getDataBook()
    {
        return dataBook;
    }

    /**
     * {@inheritDoc}
     */
    public void setDataBook(IDataBook pDataBook)
    {
        uninstallChart();
        
        dataBook = pDataBook;
        
        installChart();
    }

    /**
     * {@inheritDoc}
     */
    public void startEditing()
    {
        // Do nothing
    }

    /**
     * {@inheritDoc}
     */
    public void notifyRepaint()
    {
        if (bFirstNotifyRepaintCall)
        {
            bFirstNotifyRepaintCall = false;

            getFactory().invokeLater(this);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void saveEditing() throws ModelException
    {
        // Do nothing
    }

    /**
     * {@inheritDoc}
     */
    public void cancelEditing()
    {
        // Do nothing
    }

    /**
     * {@inheritDoc}
     */
    public TranslationMap getTranslation()
    {
        return tmpTranslation;
    }

    /**
     * {@inheritDoc}
     */
    public void setTranslation(TranslationMap pTranslation)
    {
        if (tmpTranslation != pTranslation)
        {
            uninstallChart();
            
            tmpTranslation = pTranslation;
            
            installChart();
        }
    }
    
    /**
     * {@inheritDoc}
     */
    public void setTranslationEnabled(boolean pEnabled)
    {
        bTranslationEnabled = pEnabled;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isTranslationEnabled()
    {
        return bTranslationEnabled;
    }

    /**
     * {@inheritDoc}
     */
    public String translate(String pText)
    {
        if (!bTranslationEnabled || tmpTranslation == null || pText == null)
        {
            return pText;
        }
        else
        {
            return tmpTranslation.translate(pText);
        }
    }

    /**
     * {@inheritDoc}
     */
    public int getChartStyle()
    {
        return chartStyle;
    }

    /**
     * {@inheritDoc}
     */
    public void setChartStyle(int pChartStyle)
    {
        if (pChartStyle != chartStyle)
        {
            uninstallChart();
            
            chartStyle = pChartStyle;
            
            installChart();
        }
    }

    /**
     * {@inheritDoc}
     */
    public String getTitle()
    {
        return sTitle;
    }

    /**
     * {@inheritDoc}
     */
    public void setTitle(String pTitle)
    {
        sTitle = pTitle;
    }

    /**
     * {@inheritDoc}
     */
    public String getXAxisTitle()
    {
        return sXTitle;
    }

    /**
     * {@inheritDoc}
     */
    public void setXAxisTitle(String pXAxisTitle)
    {
        sXTitle = pXAxisTitle;
    }

    /**
     * {@inheritDoc}
     */
    public String getYAxisTitle()
    {
        return sYTitle;
    }

    /**
     * {@inheritDoc}
     */
    public void setYAxisTitle(String pYAxisTitle)
    {
        sYTitle = pYAxisTitle;
    }

    /**
     * {@inheritDoc}
     */
    public String getXColumnName()
    {
        return sXColumnName;
    }

    /**
     * {@inheritDoc}
     */
    public void setXColumnName(String pXColumnName)
    {
        uninstallChart();
        
        sXColumnName = pXColumnName;
        
        installChart();
    }

    /**
     * {@inheritDoc}
     */
    public String[] getYColumnNames()
    {
        return saYColumnNames;
    }

    /**
     * {@inheritDoc}
     */
    public void setYColumnNames(String[] pYColumnNames)
    {
        uninstallChart();
        
        saYColumnNames = pYColumnNames;
        
        installChart();
    }

    /**
     * {@inheritDoc}
     */
    public void valuesChanged(DataRowEvent pDataRowEvent) throws Throwable
    {
        IDataRow originalRow = pDataRowEvent.getOriginalDataRow();
        IDataRow changedRow = pDataRowEvent.getChangedDataRow();
        
        if (!originalRow.equals(changedRow, new String [] {sXColumnName}) || !originalRow.equals(changedRow, saYColumnNames))
        {
            bIgnoreRepaint = false;
        }
    }
    
    /**
     * {@inheritDoc}
     */
    public void dataBookChanged(DataBookEvent pDataBookEvent) throws Throwable
    {
        bIgnoreRepaint = false;
    }
    
    /**
     * {@inheritDoc}
     */ 
    public void onDataPointClick(int pDatasetIndex, int pDataIndex)
    {
        try
        {
            if (getBaseStyle() == STYLE_PIE && saYColumnNames.length > 1)
            {
                dataBook.setSelectedColumn(saYColumnNames[pDataIndex]);
            }
            else
            {
                IRowDefinition rowdef = dataBook.getRowDefinition();
                IDataType dataTypeX = rowdef.getColumnDefinition(sXColumnName).getDataType();

                if (dataTypeX instanceof StringDataType)
                {
                    if (getBaseStyle() == STYLE_PIE)
                    {
                        PieChartConfig pieConfig = (PieChartConfig)config;
                        Data data = pieConfig.data();
                        String category = data.getLabels().get(pDataIndex);
                        
                        int selected = -1;
                        for (int i = 0, count = dataBook.getRowCount(); i < count && selected < 0; i++)
                        {
                            if (category.equals(translate(dataBook.getDataRow(i).getValueAsString(sXColumnName))))
                            {
                                selected = i;
                            }
                        }
                        
                        dataBook.setSelectedRow(selected);
                        dataBook.setSelectedColumn(saYColumnNames[0]);
                    }
                    else
                    {
                        Data data = null;
                        if (getBaseStyle() == STYLE_LINES || getBaseStyle() == STYLE_AREA)
                        {
                            data = ((LineChartConfig)config).data();
                        }
                        else if (getBaseStyle() == STYLE_BARS)
                        {
                            data = ((BarChartConfig)config).data();
                        }
                        ScatterDataset dataSet = (ScatterDataset)data.getDatasetAtIndex(pDatasetIndex);
                        String category = dataSet.getDataLabels().get(pDataIndex);

                        int selected = -1;
                        for (int i = 0, count = dataBook.getRowCount(); i < count && selected < 0; i++)
                        {
                            if (category.equals(translate(dataBook.getDataRow(i).getValueAsString(sXColumnName))))
                            {
                                selected = i;
                            }
                        }

                        dataBook.setSelectedRow(selected);
                        dataBook.setSelectedColumn(saYColumnNames[pDatasetIndex]);
                    }
                }
                else
                {
                    dataBook.setSelectedRow(pDataIndex);
                    if (getBaseStyle() == STYLE_PIE)
                    {
                        dataBook.setSelectedColumn(sXColumnName);
                    }
                    else
                    {
                        dataBook.setSelectedColumn(saYColumnNames[pDatasetIndex]);
                    }
                }
            }

            if (eventMousePressed != null)
            {
                getFactory().synchronizedDispatchEvent(eventMousePressed, 
                                                        new UIMouseEvent(eventSource, UIMouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, 
                                                                pDatasetIndex, pDataIndex, 1, false));
            }
            
            if (eventMouseReleased != null)
            {
                getFactory().synchronizedDispatchEvent(eventMouseReleased, 
                                                        new UIMouseEvent(eventSource, UIMouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), 0, 
                                                                pDatasetIndex, pDataIndex, 1, false));
            }
            
            if (eventMouseClicked != null)
            {
                getFactory().synchronizedDispatchEvent(eventMouseClicked, 
                                                        new UIMouseEvent(eventSource, UIMouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, 
                                                                pDatasetIndex, pDataIndex, 1, false)); 
            }
        } 
        catch (ModelException e) 
        {
            // Do Nothing
        }
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // User-defined methods
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    /**
     * Installs the chart.
     */
    private void installChart()
    {
        if (dataBook != null && sXColumnName != null && saYColumnNames != null && bAttached)
        {
            dataBook.addControl(this);
            
            dataBook.eventValuesChanged().addInternalListener(this);
            dataBook.eventAfterInserting().addInternalListener(this);
            dataBook.eventAfterDeleted().addInternalListener(this);
            dataBook.eventAfterReload().addInternalListener(this);
            if (getBaseStyle() == STYLE_PIE)
            {
                dataBook.eventAfterRowSelected().addInternalListener(this);
                dataBook.eventAfterColumnSelected().addInternalListener(this);
            }
            
            if (getBaseStyle() == IChart.STYLE_PIE)
            {
                config = createPieChartConfiguration();
            }
            else if (getBaseStyle() == IChart.STYLE_AREA)
            {
                config = createLineChartConfiguration();
            }
            else if (getBaseStyle() == IChart.STYLE_BARS)
            {
                config = createBarChartConfiguration(isHorizontalStyle());
            }
            else // if (getBaseStyle() == IChart.STYLE_LINES)
            {
                config = createLineChartConfiguration();
            }

            bIgnoreRepaint = false;
            notifyRepaint();
        }
    }

    /**
     * Creates a pie chart configuration.
     * 
     * @return the {@link ChartConfig}
     */
    private ChartConfig createPieChartConfiguration()
    {
        if (getSubStyle() == IChart.STYLE_RING)
        {
            DonutChartConfig donutConfig = new DonutChartConfig();
            
            donutConfig.options()
                            .responsive(true)
                            .title()
                                .display(true)
                                .text(translate(sTitle))
                                .and()
                            .legend().display(false);
            
            donutConfig.data().clear();
            
            donutConfig.data().addDataset(new PieDataset());

            return donutConfig;
        }
        
        PieChartConfig pieConfig = new PieChartConfig();
        
        pieConfig.options()
                    .responsive(true)
                    .title()
                        .display(true)
                        .text(translate(sTitle))
                        .and()
                    .legend().display(false);
        
        pieConfig.data().clear();
        
        pieConfig.data().addDataset(new PieDataset());
        
        return pieConfig;
    }

    /**
     * Creates a line chart configuration.
     * 
     * @return the {@link ChartConfig}
     */
    private ChartConfig createLineChartConfiguration()
    {
        LineChartConfig lineConfig = new LineChartConfig();

        lineConfig.options()
                      .responsive(true)
                      .title()
                          .display(true)
                          .text(translate(sTitle))
                          .and()
                      .legend().position(Position.BOTTOM).and()
                   .done();
        
        lineConfig.data().clear();
        
        IRowDefinition rowdef = dataBook.getRowDefinition();
        
        IDataType dataTypeX = null;
        try
        {
            dataTypeX = rowdef.getColumnDefinition(sXColumnName).getDataType();
        }
        catch (ModelException e)
        {
            ExceptionHandler.show(e);
        }
        
        BaseScale scale;
        
        boolean bTransparent = (getBaseStyle() == IChart.STYLE_AREA && !(getSubStyle() == IChart.STYLE_STACKEDAREA || getSubStyle() == IChart.STYLE_STACKEDPERCENTAREA));
        
        if (dataTypeX instanceof TimestampDataType)
        {
            scale = new TimeScale();
            
            for (int i = 0; i < saYColumnNames.length; i++)
            {
                TimeLineDataset dataSet = new TimeLineDataset();
                
                dataSet.borderColor(getColor(i, bTransparent));
                dataSet.backgroundColor(getColor(i, bTransparent));
                dataSet.fill(getBaseStyle() == IChart.STYLE_AREA);
                
                if (getSubStyle() == IChart.STYLE_STEPLINES)
                {
                    dataSet.steppedLine(true);
                }
                
                dataSet.lineTension(0);
                dataSet.borderWidth(1);
                
                if (getBaseStyle() == IChart.STYLE_LINES && getSubStyle() != IChart.STYLE_STEPLINES)
                {
                    dataSet.pointStyle(PointStyle.valueOf(getPointStyle(i)));
                }
                else
                {
                    dataSet.pointRadius(Integer.valueOf(0));
                }
                
                lineConfig.data().addDataset(dataSet);
            }
        }
        else if (dataTypeX instanceof BigDecimalDataType || dataTypeX instanceof LongDataType)
        {
            scale = new LinearScale();
            
            for (int i = 0; i < saYColumnNames.length; i++)
            {
                ScatterDataset dataSet = new ScatterDataset();
                
                dataSet.borderColor(getColor(i, bTransparent));
                dataSet.backgroundColor(getColor(i, bTransparent));
                dataSet.fill(getBaseStyle() == IChart.STYLE_AREA);
                
                if (getSubStyle() == IChart.STYLE_STEPLINES)
                {
                    dataSet.steppedLine(true);
                }
                
                dataSet.lineTension(0);
                dataSet.borderWidth(1);
                
                if (getBaseStyle() == IChart.STYLE_LINES && getSubStyle() != IChart.STYLE_STEPLINES)
                {
                    dataSet.pointStyle(PointStyle.valueOf(getPointStyle(i)));
                    dataSet.pointRadius(Integer.valueOf(4));
                    dataSet.pointHitRadius(Integer.valueOf(7));
                }
                else
                {
                    dataSet.pointRadius(Integer.valueOf(0));
                }
                
                lineConfig.data().addDataset(dataSet);
            }
        } 
        else
        {
            scale = new CategoryScale();
            
            for (int i = 0; i < saYColumnNames.length; i++)
            {
                LineDataset dataSet = new LineDataset();
                
                dataSet.borderColor(getColor(i, bTransparent));
                dataSet.backgroundColor(getColor(i, bTransparent));
                dataSet.fill(getBaseStyle() == IChart.STYLE_AREA);
                
                if (getSubStyle() == IChart.STYLE_STEPLINES)
                {
                    dataSet.steppedLine(true);
                }
                
                dataSet.lineTension(0);
                dataSet.borderWidth(1);
                
                if (getBaseStyle() == IChart.STYLE_LINES && getSubStyle() != IChart.STYLE_STEPLINES)
                {
                    dataSet.pointStyle(PointStyle.valueOf(getPointStyle(i)));
                }
                else
                {
                    dataSet.pointRadius(Integer.valueOf(0));
                }
                
                lineConfig.data().addDataset(dataSet);
            }
        }
        
        scale.scaleLabel().display(true).labelString(translate(getXAxisTitle()));
        
        lineConfig.options()
        .scales()
        .add(Axis.X, scale);
        
        return lineConfig;
    }

    /**
     * Creates a bar chart configuration.
     * 
     * @param pHorizontalStyle whether bar chart should be horizontal
     * @return the bar {@link ChartConfig}
     */
    private ChartConfig createBarChartConfiguration(boolean pHorizontalStyle)
    {
        BarChartConfig barConfig = new BarChartConfig();
        
        if (pHorizontalStyle)
        {
            barConfig.horizontal();
        }
        
        barConfig.data().clear();
        
        barConfig.options()
                    .responsive(true)
                    .title()
                        .display(true)
                        .text(translate(sTitle))
                        .and()
                        .legend().position(Position.BOTTOM).and()
                    .done();
        
        CategoryScale scale = new CategoryScale();
        scale.stacked(isBarStackedStyle());
        scale.scaleLabel().display(true).labelString(translate(getXAxisTitle()));
        
        barConfig.data().extractLabelsFromDataset(true);
        
        barConfig.options()
                        .scales()
                        .add(isHorizontalStyle() ? Axis.Y : Axis.X, scale);
        
        for (int i = 0; i < saYColumnNames.length; i++)
        {
            BarDataset dataSet = new BarDataset();
            
            if (getSubStyle() == IChart.STYLE_OVERLAPPEDBARS || getSubStyle() == IChart.STYLE_OVERLAPPEDHBARS)
            {
                dataSet.borderColor(getColor(i));
            }
            
            dataSet.backgroundColor(getColor(i));
            
            barConfig.data().addDataset(dataSet);
        }
        
        return barConfig;
    }

    /**
     * Uninstalls the chart.
     */
    private void uninstallChart()
    {
        if (dataBook != null && bAttached)
        {
            dataBook.removeControl(this);
            dataBook.eventValuesChanged().removeInternalListener(this);
            dataBook.eventAfterInserting().removeInternalListener(this);
            dataBook.eventAfterDeleted().removeInternalListener(this);
            dataBook.eventAfterReload().removeInternalListener(this);
            if (getBaseStyle() == STYLE_PIE)
            {
                dataBook.eventAfterRowSelected().removeInternalListener(this);
                dataBook.eventAfterColumnSelected().removeInternalListener(this);
            }
        }
    }
    
    /**
     * Initializes the DataSeries.
     * 
     * @throws ModelException if initializing fails
     */
    private void initializeData() throws ModelException
    {
        if (bAttached && (!bIgnoreRepaint || currentDataPage != dataBook.getDataPage()))
        {
            currentDataPage = dataBook.getDataPage();
            bIgnoreRepaint = true;
            
            IRowDefinition rowdef = dataBook.getRowDefinition();
            
            if (getBaseStyle() == STYLE_LINES || getBaseStyle() == STYLE_AREA)
            {
                initializeLineOrAreaData(rowdef);
            }
            else if (getBaseStyle() == STYLE_BARS)
            {
                initializeBarData(rowdef);
            }
            else if (getBaseStyle() == STYLE_PIE)
            {
                initializePieData(rowdef);
            }
            
            resource.update();
        }
    }

    /**
     * Fills pie data set with values from data row.
     * 
     * @param pRowDefinition the {@link IRowDefinition}
     * @param pDataRow the {@link IDataRow}
     * @param pDataset the {@link PieDataset}
     * @param pLabel the label
     * @throws ModelException if fill fails
     */
    private void fillPieDataSet(IRowDefinition pRowDefinition, IDataRow pDataRow, PieDataset pDataset, String pLabel) throws ModelException
    {
        ArrayUtil auColors = new ArrayUtil();
        
        BigDecimal bdPieSum = BigDecimal.ZERO;
        
        for (String yColumnName : saYColumnNames)
        {
            Number numValue = (Number)pDataRow.getValue(yColumnName);
            
            if (numValue != null)
            {
            	bdPieSum = bdPieSum.add(NumberUtil.toBigDecimal(numValue));
            }
        }
        
        for (String yColumnName : saYColumnNames)
        {
            String yColumnTitle = translate(pRowDefinition.getColumnDefinition(yColumnName).getLabel());
            Number numValue = (Number)pDataRow.getValue(yColumnName);
            
            if (numValue != null)
            {
                pDataset.addLabeledData(pLabel == null ? yColumnTitle : pLabel, calculatePercentValue(bdPieSum, NumberUtil.toBigDecimal(numValue)));
                auColors.add(getColor(auColors.size()));
            }
        }
        
        if (pLabel == null)
        {
            pDataset.backgroundColor(auColors.toArray(new String[auColors.size()]));
        }
    }
    
    /**
     * Calculates the sums and gets them.
     * 
     * @return the sums {@link Map}
     * @throws ModelException if it fails
     */
    private HashMap> getSums() throws ModelException
    {
        LinkedHashMap> hmSumMaps = new LinkedHashMap>();
        
        for (int i = 0; i < dataBook.getRowCount(); i++)
        {
            IDataRow dr = dataBook.getDataRow(i);
            
            String sValueX = dr.getValueAsString(sXColumnName);
            
            for (int y = 0; y < saYColumnNames.length; y++)
            {
                BigDecimal bdValueY = NumberUtil.toBigDecimal((Number)dr.getValue(saYColumnNames[y]));
                
                if (bdValueY != null)
                {
                    if (hmSumMaps.containsKey(sValueX))
                    {
                        HashMap hmSum = hmSumMaps.get(sValueX);
                        
                        BigDecimal bdYSum = hmSum.get(saYColumnNames[y]);
                        
                        if (bdYSum == null)
                        {
                            hmSum.put(saYColumnNames[y], bdValueY);
                        }
                        else
                        {
                            hmSum.put(saYColumnNames[y], bdYSum.add(bdValueY));
                        }
                        
                        BigDecimal bdAllSum = hmSum.get(SUM_ALL_KEY);
                        
                        if (bdAllSum == null)
                        {
                            hmSum.put(SUM_ALL_KEY, bdValueY);
                        }
                        else
                        {
                            hmSum.put(SUM_ALL_KEY, bdAllSum.add(bdValueY));
                        }
                    }
                    else
                    {
                        HashMap hmSum = new HashMap();
                        
                        hmSum.put(saYColumnNames[y], bdValueY);
                        hmSum.put(SUM_ALL_KEY, bdValueY);
                        
                        hmSumMaps.put(sValueX, hmSum);
                    }
                }
            }
        }
        
        return hmSumMaps;
    }

    /**
     * Gets the base style of the chart (the lowest 2 digits of constant).
     * 
     * @return the chart style ({@link #STYLE_LINES}, {@link #STYLE_AREA}, {@link #STYLE_BARS}).
     */
    public int getBaseStyle()
    {
        return chartStyle % 100;
    }

    /**
     * Gets the sub style of the chart (the lowest 2 digits of constant).
     * 
     * @return the base style ({@link #STYLE_LINES}, {@link #STYLE_AREA}, {@link #STYLE_BARS}).
     */
    public int getSubStyle()
    {
        return chartStyle % 1000;
    }

    /**
     * Gets the sub style of the chart (the lowest 2 digits of constant).
     * 
     * @return the base style ({@link #STYLE_LINES}, {@link #STYLE_AREA}, {@link #STYLE_BARS}).
     */
    public boolean isHorizontalStyle()
    {
        return chartStyle / 1000 != 0;
    }
    
    /**
     * Whether is bar stacked style or not.
     * 
     * @return true if the style is stacked bar, otherwise false
     */
    public boolean isBarStackedStyle()
    {
        int iSubStyle = getSubStyle();
        
        return iSubStyle == IChart.STYLE_STACKEDBARS
                                || iSubStyle == IChart.STYLE_STACKEDHBARS
                                || iSubStyle == IChart.STYLE_STACKEDPERCENTBARS
                                || iSubStyle == IChart.STYLE_STACKEDPERCENTHBARS
                                || iSubStyle == IChart.STYLE_OVERLAPPEDBARS
                                || iSubStyle == IChart.STYLE_OVERLAPPEDHBARS;
    }
    
    /**
     * Gets the non transparent color.
     * 
     * @param pIndex the index
     * @return the color string
     */
    public String getColor(int pIndex)
    {
        return getColor(pIndex, false);
    }
    
    /**
     * Gets the color.
     * 
     * @param pIndex the index
     * @param pTransparent whether the color should be transparent
     * @return the color string
     */
    public String getColor(int pIndex, boolean pTransparent)
    {
        if (pTransparent)
        {
            return TRANSPARENT_COLORS[pIndex % TRANSPARENT_COLORS.length];
        }
        else
        {
            return COLORS[pIndex % COLORS.length];
        }
    }
    
    /**
     * Gets the chart point style.
     * 
     * @param pIndex the index
     * @return the point style name
     */
    private String getPointStyle(int pIndex)
    {
        return POINT_STYLES[pIndex % POINT_STYLES.length];
    }
    
    /**
     * Initializes line or area data.
     * 
     * @param pRowDefinition the {@link IRowDefinition}
     * @throws ModelException if initialization fails
     */
    private void initializeLineOrAreaData(IRowDefinition pRowDefinition) throws ModelException
    {
        LineChartConfig lineConfig = (LineChartConfig)config;
        
        LinearScale linearScale;
        
        dataBook.fetchAll();
   
        IDataType dataTypeX = pRowDefinition.getColumnDefinition(sXColumnName).getDataType();
        
        ArrayList categories = new ArrayList();
        
        for (int i = 0; i < dataBook.getRowCount(); i++)
        {
            IDataRow row = dataBook.getDataRow(i);
      
            Object xValue = row.getValue(sXColumnName);
            
            if (xValue == null)
            {
                continue;
            }
            
            String sValueX = row.getValueAsString(sXColumnName);
            
            if (!categories.contains(sValueX))
            {
                categories.add(sValueX);
            }
            
            Map> mSums = getSums();
            
            BigDecimal bdPrev = null;
            Double dPercentage = null;
            
            for (int y = 0; y < saYColumnNames.length; y++)
            {
                BigDecimal yValue = NumberUtil.toBigDecimal((Number)row.getValue(saYColumnNames[y]));
                
                if (getSubStyle() == IChart.STYLE_STACKEDPERCENTAREA)
                {
                    if (bdPrev == null)
                    {
                        bdPrev = yValue;
                    }
                    else
                    {
                        yValue = bdPrev.add(yValue);
                        bdPrev = yValue;
                    }
                    
                    if (y == (saYColumnNames.length - 1))
                    {
                        dPercentage = Double.valueOf(100);
                    }
                    else
                    {
                        dPercentage = calculatePercentValue(mSums.get(sValueX).get(SUM_ALL_KEY), yValue);
                    }
                }
                
                if (dataTypeX instanceof TimestampDataType)
                {
                    LocalDateTime ldtDateX = Instant.ofEpochMilli(((Date)xValue).getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
                    
                    TimeLineDataset dataSet = (TimeLineDataset)lineConfig.data().getDatasetAtIndex(y);
                    dataSet.label(translate(pRowDefinition.getColumnDefinition(saYColumnNames[y]).getLabel()));
                    
                    if (getSubStyle() == IChart.STYLE_STACKEDPERCENTAREA)
                    {
                        dataSet.addData(ldtDateX, dPercentage);
                    }
                    else
                    {
                        dataSet.addData(ldtDateX, Double.valueOf(yValue.doubleValue()));
                    }
                }
                else if (dataTypeX instanceof BigDecimalDataType || dataTypeX instanceof LongDataType)
                {
                    Number numValueX = (Number)xValue;
                    
                    ScatterDataset dataSet = (ScatterDataset)lineConfig.data().getDatasetAtIndex(y);
                    dataSet.label(translate(pRowDefinition.getColumnDefinition(saYColumnNames[y]).getLabel()));
                    
                    if (getSubStyle() == IChart.STYLE_STACKEDPERCENTAREA)
                    {
                        dataSet.addData(Double.valueOf(numValueX.doubleValue()), dPercentage);
                    }
                    else
                    {
                        dataSet.addData(Double.valueOf(numValueX.doubleValue()), Double.valueOf(yValue.doubleValue()));
                    }
                }
                else
                {
                    LineDataset dataSet = (LineDataset)lineConfig.data().getDatasetAtIndex(y);
                    dataSet.label(translate(pRowDefinition.getColumnDefinition(saYColumnNames[y]).getLabel()));
                    
                    if (getSubStyle() == IChart.STYLE_STACKEDPERCENTAREA)
                    {
                        dataSet.addLabeledData(sValueX, dPercentage);
                    }
                    else
                    {
                        dataSet.addLabeledData(sValueX, Double.valueOf(yValue.doubleValue()));
                    }
                    
                    lineConfig.data().extractLabelsFromDataset(true);
                }
            }
        }
        
        linearScale = new LinearScale();
        
        if (getSubStyle() == IChart.STYLE_STACKEDAREA)
        {
            linearScale.stacked(true);
        }
        
        linearScale.scaleLabel().display(true).labelString(translate(getYAxisTitle()));
        
        if (getSubStyle() == IChart.STYLE_STACKEDPERCENTAREA)
        {
            LinearTicks ticks = linearScale.ticks();
            
            ticks.beginAtZero(Boolean.TRUE);
            ticks.max(100);
            ticks.callback("function(value) { return value + '%' }");
        }
        
        lineConfig.options().scales().add(Axis.Y, linearScale);
        
        logger.debug(lineConfig.buildJson());
        
        resource.configure(lineConfig);
        
        resource.setHeight(getPreferredSize().getHeight(), Unit.PIXELS);
        resource.setWidth(getPreferredSize().getWidth(), Unit.PIXELS);
    }

    /**
     * Initializes bar data.
     * 
     * @param pRowDefinition the {@link IRowDefinition}
     * @throws ModelException if initialization fails
     */
    private void initializeBarData(IRowDefinition pRowDefinition) throws ModelException
    {
        BarChartConfig barConfig = (BarChartConfig)config;
        
        dataBook.fetchAll();
        
        HashMap> hmSumMaps = getSums();
        
        for (int i = 0; i < dataBook.getRowCount(); i++)
        {
            IDataRow row = dataBook.getDataRow(i);
            
            Object xValue = row.getValue(sXColumnName);
            
            if (xValue == null)
            {
                continue;
            }
            
            String sValueX = row.getValueAsString(sXColumnName);
            
            for (int y = 0; y < saYColumnNames.length; y++)
            {
                BigDecimal yValue = NumberUtil.toBigDecimal((Number)row.getValue(saYColumnNames[y]));
                
                BarDataset dataSet = (BarDataset)barConfig.data().getDatasetAtIndex(y);
                
                if (getSubStyle() != IChart.STYLE_STACKEDPERCENTBARS && getSubStyle() != IChart.STYLE_STACKEDPERCENTHBARS)
                {
                    dataSet.addLabeledData(sValueX, Double.valueOf(yValue.doubleValue()));
                }
                
                dataSet.label(translate(pRowDefinition.getColumnDefinition(saYColumnNames[y]).getLabel()));
            }
        }
        
        LinearScale ls = new LinearScale();
        
        if (getSubStyle() == IChart.STYLE_STACKEDBARS || getSubStyle() == IChart.STYLE_STACKEDPERCENTBARS)
        {
            ls.stacked(true);
        }
        
        ls.scaleLabel().display(true).labelString(translate(getYAxisTitle()));
        
        if (getSubStyle() == IChart.STYLE_STACKEDPERCENTBARS || getSubStyle() == IChart.STYLE_STACKEDPERCENTHBARS)
        {
            LinearTicks ticks = ls.ticks();
            
            ticks.beginAtZero(Boolean.TRUE);
            ticks.max(100);
            ticks.callback("function(value) { return value + '%' }");
            
            for (Entry> entry : hmSumMaps.entrySet())
            {
                for (int y = 0; y < saYColumnNames.length; y++)
                {
                    BarDataset dataSet = (BarDataset)barConfig.data().getDatasetAtIndex(y);
                    
                    dataSet.addLabeledData(entry.getKey(), calculatePercentValue(entry.getValue().get(SUM_ALL_KEY), entry.getValue().get(saYColumnNames[y])));
                }
            }
        }
        
        if (isHorizontalStyle())
        {
            ls.position(Position.TOP);
        }
        
        barConfig.options().scales().add(isHorizontalStyle() ? Axis.X : Axis.Y, ls);
        
        resource.configure(barConfig);
        
        resource.setHeight(getPreferredSize().getHeight(), Unit.PIXELS);
        resource.setWidth(getPreferredSize().getWidth(), Unit.PIXELS);
        
        logger.debug(barConfig.buildJson());
    }
    
    /**
     * Initializes pie data.
     * 
     * @param pRowDefinition the {@link IRowDefinition}
     * @throws ModelException if initialization fails
     */
    private void initializePieData(IRowDefinition pRowDefinition) throws ModelException
    {
        Data data;
        
        if (getSubStyle() == STYLE_RING)
        {
            DonutChartConfig donutConfig = (DonutChartConfig)config;
            donutConfig.options().tooltips().callbacks().label(createTooltipJsFunction());
            
            data = donutConfig.data();
        }
        else
        {
            PieChartConfig pieConfig = (PieChartConfig)config;
            pieConfig.options().tooltips().callbacks().label(createTooltipJsFunction());
            
            data = pieConfig.data();
        }
        
        if (saYColumnNames.length > 1)
        {
            fillPieDataSet(pRowDefinition, dataBook, (PieDataset)data.getDatasetAtIndex(0), null);
            
            data.labelsAsList(Arrays.asList(saYColumnNames));
        }
        else
        {
            ArrayUtil auColors = new ArrayUtil();
            ArrayList auCategories = new ArrayList();
            
            PieDataset dataSet = (PieDataset)data.getDatasetAtIndex(0);
            
            HashMap> hmSumMaps = getSums();
            
            dataBook.fetchAll();
            
            for (int i = 0; i < dataBook.getRowCount(); i++)
            {
                IDataRow row = dataBook.getDataRow(i);
         
                String sValueX = row.getValueAsString(sXColumnName);
                
                if (!auCategories.contains(sValueX))
                {
                    auCategories.add(sValueX);
                    auColors.add(getColor(auColors.size()));
                }
            }
            
            dataSet.backgroundColor(auColors.toArray(new String[auColors.size()]));
            data.labelsAsList(auCategories);
            
            BigDecimal bdPieSum = BigDecimal.ZERO;
            
            for (Entry> entry : hmSumMaps.entrySet())
            {
                bdPieSum = bdPieSum.add(entry.getValue().get(SUM_ALL_KEY));
            }
            
            for (Entry> entry : hmSumMaps.entrySet())
            {
                for (int y = 0; y < saYColumnNames.length; y++)
                {
                    dataSet.addLabeledData(entry.getKey(), calculatePercentValue(bdPieSum, entry.getValue().get(saYColumnNames[y])));
                }
            }
        }
        
        resource.configure(config);
        
        resource.setHeight(getPreferredSize().getHeight(), Unit.PIXELS);
        resource.setWidth(getPreferredSize().getWidth(), Unit.PIXELS);
        
        logger.debug(config.buildJson());
    }
    
    /**
     * Calculates percent value.
     * 
     * @param pSum the sum
     * @param pValue the value
     * @return the percent value
     */
    private Double calculatePercentValue(BigDecimal pSum, BigDecimal pValue)
    {
        return Double.valueOf(pValue.multiply(BigDecimal.valueOf(100)).divide(pSum, 2, RoundingMode.HALF_UP).doubleValue());
    }
    
    /**
     * Creates the tooltip javascript function.
     * 
     * @return the function code
     */
    private String createTooltipJsFunction()
    {
        return "function(tooltipItem, data) { "
                + "var label = data.labels[tooltipItem.index];"
                
                + " if (label)"
                + " {"
                + "     label += ': ';"
                + " }"
                
                + " label += data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index] + '%';"
                
                + " return label; }";
    }

}   // VaadinChartJs




© 2015 - 2025 Weber Informatics LLC | Privacy Policy