Aloha2DChart.java

// @formatter:off
 /*******************************************************************************
 *
 * This file is part of JMad.
 * 
 * Copyright (c) 2008-2011, CERN. All rights reserved.
 *
 * 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.
 * 
 ******************************************************************************/
// @formatter:on

package cern.accsoft.steering.util.gui.dv.ds;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import cern.accsoft.steering.jmad.gui.dv.MarkableChart;
import cern.accsoft.steering.util.StatUtil;
import cern.jdve.ChartRenderer;
import cern.jdve.EditionManager;
import cern.jdve.Style;
import cern.jdve.data.DataSet;
import cern.jdve.data.DataSource;
import cern.jdve.data.DefaultDataSet;
import cern.jdve.data.DefaultDataSource;
import cern.jdve.event.DataSetEvent;
import cern.jdve.event.DataSetListener;
import cern.jdve.event.DataSourceEvent;
import cern.jdve.event.DataSourceListener;
import cern.jdve.graphic.ChartAnnotation;
import cern.jdve.renderer.HiLoRenderer;

/**
 * This class is derived from a MarkerXProvider-chart, which is defined in jmad, in order to provide some additional
 * functions, we use in aloha. Here we provide an easy way to access the renderers, which each of them will have a
 * dedicated role in Aloha.
 * 
 * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
 */
public class Aloha2DChart extends MarkableChart {
    private static final long serialVersionUID = 7117238469835822463L;

    /** here we display the mean and rms */
    private ChartAnnotation commentAnnotation = null;

    /** determines, if stats shall be shown, or not */
    private boolean visibleStatistics = true;

    /**
     * the default constructor, which creates the necessary renderers with default settings.
     */
    public Aloha2DChart() {
        super();
        initDefaultConfig();
        createRenderers();
        createDefaultInteractors();
    }

    /**
     * just sets the default-values
     */
    private void initDefaultConfig() {
        super.setAntiAliasingText(true);
    }

    public void setVisibleCategory(boolean visibleCategory) {
        super.getXScale().setLabelRotation((visibleCategory ? 90 : 0));
        super.getXScale().setCategory(visibleCategory);
    }

    public boolean isVisibleCategory() {
        return super.getXScale().isCategory();
    }

    /**
     * create the default renderers and set empty dataSets to them.
     */
    private void createRenderers() {
        /*
         * one renderer for each role:
         */
        /* MEASUREMENT_DATA */
        addEmptyRenderer(styleRenderer(DvUtils.createBarChartRenderer(), ChartRendererRole.MEAS_DATA));
        /* MEAS_ERROR */
        addEmptyRenderer(styleRenderer(DvUtils.createHiLoRenderer(), ChartRendererRole.MEAS_ERROR));
        /* MODEL_DATA */
        addEmptyRenderer(styleRenderer(DvUtils.createMarkerPolylineRenderer(), ChartRendererRole.MODEL_DATA));
        /* MEASUREMENT_FIT */
        addEmptyRenderer(styleRenderer(DvUtils.createPolyLineRenderer(), ChartRendererRole.MEAS_FIT));
        /* MODEL_FIT */
        addEmptyRenderer(styleRenderer(DvUtils.createPolyLineRenderer(), ChartRendererRole.MODEL_FIT));
    }

    /**
     * returns the correct style for the given role
     * 
     * @param role
     * @return
     */
    private ChartRenderer styleRenderer(ChartRenderer renderer, ChartRendererRole role) {
        Color color = null;
        Color strokeColor = null;
        if (ChartRendererRole.MEAS_DATA.equals(role)) {
            color = ColorConstants.COLOR_MEAS_DATA_FILL;
            strokeColor = ColorConstants.COLOR_MEAS_DATA_STROKE;
        } else if (ChartRendererRole.MEAS_ERROR.equals(role)) {
            color = ColorConstants.COLOR_MEAS_ERROR_FILL;
            strokeColor = ColorConstants.COLOR_MEAS_ERROR_STROKE;
        } else if (ChartRendererRole.MODEL_DATA.equals(role)) {
            color = ColorConstants.COLOR_MODEL_DATA;
            strokeColor = color.darker();
        }

        if (color != null) {
            renderer.setStyles(new Style[] { new Style(strokeColor, color) });
        }

        return renderer;
    }

    /**
     * adds the default interactors for an aloha-chart
     */
    public void createDefaultInteractors() {
        DvUtils.configureDefaultInteractors(this);
    }

    /**
     * sets an empty dataSet to the given renderer and adds it to the chart.
     * 
     * @param renderer the renderer which to add to the chart.
     */
    private void addEmptyRenderer(ChartRenderer renderer) {
        if (renderer instanceof HiLoRenderer) {
            renderer.setDataSource(new DefaultDataSource(new DataSet[] { createEmptyDataSet(), createEmptyDataSet() }));
        } else {
            renderer.setDataSet(createEmptyDataSet());
        }
        addRenderer(renderer);
    }

    /**
     * sets a new dataSet to the renderer of the given role.
     * 
     * @param role the role of the renderer for which to set the new DataSet
     * @param dataSet the new DataSet which to set to the renderer
     */
    public void setRendererDataSet(ChartRendererRole role, DataSet dataSet) {
        ChartRenderer renderer = getRenderer(role);
        renderer.setDataSet(dataSet);

        if (ChartRendererRole.MEAS_DATA.equals(role)) {
            dataSet.addDataSetListener(new DataSetListener() {

                @Override
                public void dataSetChanged(DataSetEvent evt) {
                    updateStatistics();
                }
            });
        }
        updateStatistics();
    }

    /**
     * sets a new dataSource to the renderer of a given Role
     * 
     * @param role the role of the Renderer to which to set the dataSource
     * @param dataSource the {@link DataSource} to set
     */
    public void setRenderDataSource(ChartRendererRole role, DataSource dataSource) {
        ChartRenderer renderer = getRenderer(role);
        renderer.setDataSource(dataSource);

        if (ChartRendererRole.MEAS_DATA.equals(role)) {
            dataSource.addDataSourceListener(new DataSourceListener() {

                @Override
                public void dataSourceChanged(DataSourceEvent evt) {
                    updateStatistics();
                }
            });
        }
        updateStatistics();
    }

    /**
     * sets the renderer as empty
     */
    public void clearRenderer(ChartRendererRole role) {
        setRendererDataSet(role, createEmptyDataSet());
    }

    /**
     * creates an empty dataSet which is used as default for the renderers.
     * 
     * @return the empty DataSet.
     */
    private DataSet createEmptyDataSet() {
        return new DefaultDataSet("empty", new double[] {}, new double[] {});
    }

    /**
     * @param role the {@link ChartRendererRole} for which to retrieve the renderer.
     * @return the renderer for that role.
     */
    public ChartRenderer getRenderer(ChartRendererRole role) {
        return super.getRenderer(role.ordinal());
    }

    /**
     * exchanges the renderer of the given role with the new one.
     * 
     * @param role
     * @param renderer
     */
    public void setRenderer(ChartRendererRole role, ChartRenderer renderer) {
        super.setRenderer(role.ordinal(), styleRenderer(renderer, role));
        updateEditablePlots();
    }

    /**
     * replace the renderer for the given Role with a renderer of another type
     * 
     * @param role the role of the renderer
     * @param rendererType the type of the renderer
     */
    public void setRendererType(ChartRendererRole role, int rendererType) {
        setRendererType(role.ordinal(), rendererType);
    }

    /**
     * set the renderer-type for a given role, by Enum
     * 
     * @param role the role for which to set the new renderer
     * @param type the type of renderer to set.
     */
    public void setRendererType(ChartRendererRole role, RendererType type) {
        setRendererType(role, type.getDvIntValue());
    }

    /**
     * @param role the role for which to retrieve the renderer-type
     * @return the {@link RendererType} for the given renderer-role.
     */
    public RendererType getRendererType(ChartRendererRole role) {
        int intValue = ChartRenderer.getRendererType(getRenderer(role));
        return RendererType.fromDvIntValue(intValue);
    }

    @Override
    public void setRendererType(int index, int rendererType) {
        /*
         * for some types we want to use our costumized renderers.
         */
        if (rendererType == ChartRenderer.BAR) {
            setRenderer(index, DvUtils.createBarChartRenderer());
        } else if (rendererType == ChartRenderer.POLYLINE_WITH_MARKERS) {
            setRenderer(index, DvUtils.createMarkerPolylineRenderer());
        } else if (rendererType == ChartRenderer.POLYLINE) {
            setRenderer(index, DvUtils.createPolyLineRenderer());
        } else if (rendererType == ChartRenderer.SCATTER) {
            setRenderer(index, DvUtils.createScatterRenderer());
        } else {
            /* if we do not have a custom one, we use the default method. */
            super.setRendererType(index, rendererType);
        }
        updateEditablePlots();
    }

    /**
     * this methods causes e.g. the refill of the combo-box for editable datasets.
     */
    private final void updateEditablePlots() {
        EditionManager editionManager = getEditionManager();
        if (editionManager != null) {
            /*
             * XXX ??? we get an error here!
             */
            // editionManager.updateEditablePlots();
        }
    }

    /**
     * @param renderer the renderer for which to determine the role.
     * @return the role of the renderer, if it is contained in this chart, otherwise null
     */
    public ChartRendererRole getRendererRole(ChartRenderer renderer) {
        for (int i = 0; i < getRenderersCount(); i++) {
            ChartRenderer chartRenderer = getRenderer(i);
            if (chartRenderer == renderer) {
                return ChartRendererRole.values()[i];
            }
        }
        return null;
    }

    /**
     * draws the new stats for the plot
     */
    private void updateStatistics() {
        if (this.visibleStatistics) {
            if (this.commentAnnotation == null) {
                this.commentAnnotation = DvUtils.addComment(this, " ");
            }
            this.commentAnnotation.setText(getStatText());
        } else {
            if (this.commentAnnotation != null) {
                removeDecoration(this.commentAnnotation);
                this.commentAnnotation = null;
                repaintChart();
            }
        }
    }

    /**
     * @return a text with statistical info for the measurement-data
     */
    private String getStatText() {
        ChartRenderer dataRenderer = getRenderer(ChartRendererRole.MEAS_DATA);
        if ((dataRenderer == null) || (dataRenderer.getDataSource() == null)) {
            return "";
        }

        DataSet dataSet = dataRenderer.getDataSource().getDataSet(0);
        if (dataSet == null) {
            return "";
        }

        List<Double> values = new ArrayList<Double>();
        for (int i = 0; i < dataSet.getDataCount(); i++) {
            if ((dataSet instanceof ValidityDataSet) && (((ValidityDataSet) dataSet).hasValidityInformation())) {
                if (!((ValidityDataSet) dataSet).getValidity(i)) {
                    continue;
                }
            }
            values.add(dataSet.getY(i));
        }

        double[] valuesArray = new double[values.size()];
        for (int i = 0; i < valuesArray.length; i++) {
            valuesArray[i] = values.get(i);
        }
        return StatUtil.createMeanRmsString(valuesArray);
    }

    /**
     * @param visibleStatistics the visibleStatistics to set
     */
    public void setVisibleStatistics(boolean visibleStatistics) {
        this.visibleStatistics = visibleStatistics;
        updateStatistics();
    }

    /**
     * @return the visibleStatistics
     */
    public boolean isVisibleStatistics() {
        return visibleStatistics;
    }

    /**
     * this is the enum, which will define the roles for different renderers in ALOHA.
     * 
     * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
     */
    public enum ChartRendererRole {
        MEAS_DATA, MEAS_ERROR, MODEL_DATA, MEAS_FIT, MODEL_FIT;
    }

    /**
     * this enum encapsulates the int-constants used in dataViewer into an enum
     * 
     * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
     */
    public enum RendererType {

        POLYLINE(ChartRenderer.POLYLINE), POLYLINE_WITH_MARKERS(ChartRenderer.POLYLINE_WITH_MARKERS), SCATTER(
                ChartRenderer.SCATTER), BAR(ChartRenderer.BAR), IMPULSES(ChartRenderer.IMPULSES), AREA(
                ChartRenderer.AREA), DIFF_AREA(ChartRenderer.DIFF_AREA), STAIRS(ChartRenderer.STAIRS), CONTOUR(
                ChartRenderer.CONTOUR), BOOLEAN(ChartRenderer.BOOLEAN), HI_LO(ChartRenderer.HI_LO);

        private int dvIntValue = 0;

        /**
         * determines the RendererType - value out of a given integer used by the dataviewer
         * 
         * @param value the value for which to find the enum
         * @return the type of the renderer as enum
         */
        public final static RendererType fromDvIntValue(int value) {
            for (RendererType type : RendererType.values()) {
                if (type.getDvIntValue() == value) {
                    return type;
                }
            }
            return null;
        }

        /**
         * the constructor, which also sets the Dv-integer value
         * 
         * @param dvIntValue
         */
        private RendererType(int dvIntValue) {
            this.dvIntValue = dvIntValue;
        }

        /**
         * @return the dvIntValue
         */
        public int getDvIntValue() {
            return dvIntValue;
        }
    }

}