MarkableChart.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.jmad.gui.dv;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.SwingConstants;
import javax.swing.border.LineBorder;

import cern.accsoft.steering.jmad.gui.mark.MarkedElementsManager;
import cern.accsoft.steering.jmad.gui.mark.MarkedElementsManagerListener;
import cern.accsoft.steering.jmad.gui.mark.MarkerXProvider;
import cern.jdve.Chart;
import cern.jdve.LabelRenderer;
import cern.jdve.Style;
import cern.jdve.data.DataSet;
import cern.jdve.data.DataSource;
import cern.jdve.graphic.DataIndicator;
import cern.jdve.utils.DataRange;

/**
 * This chart is a chart which listens on the MarkedElementsManager, in order to display all the elements as markers.
 * 
 * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
 */
public class MarkableChart extends Chart {
    private static final long serialVersionUID = -6909388067830384883L;

    /** the manager, which keeps track of all */
    private MarkedElementsManager markedElementsManager;

    /**
     * with this map we keep track of the added markers, in order to be able to remove/replace them by name afterwards
     */
    private Map<String, List<DataIndicator>> markerDataIndicators = new HashMap<String, List<DataIndicator>>();

    /**
     * if this is set, then the x-positions of the markers are fetched from here. Otherwise the datasets are searched
     * for a valid marker-position.
     */
    private MarkerXProvider markerXProvider = null;

    /** the default font for an point indicator in the chart. */
    private final static Font DEFAULT_INDICATOR_FONT = new Font("Dialog", Font.BOLD, 10);

    /** the default style of the indicator in the chart */
    private static final Style DEFAULT_INDICATOR_STYLE = new Style(new BasicStroke(1, BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_BEVEL, 1, new float[] { 2, 2 }, 2), Color.GRAY, Color.BLACK);

    /** a light red */
    private static final Color COLOR_H_RANGE = new Color(227, 151, 89, 50);

    /** a light blue */
    private static final Color COLOR_V_RANGE = new Color(89, 188, 227, 50);

    /** the font for the ranges */
    private final static Font DEFAULT_FONT_RANGE = new Font("Dialog", Font.BOLD, 20);

    /** shows / hides the H/V indicators */
    private boolean visibleHVIndicators = false;

    private DataIndicator hRangeIndicator = null;
    private DataIndicator vRangeIndicator = null;

    /**
     * the init method, which shall be called when the {@link MarkedElementsManager} was set. It creates all markers,
     * according to the manager. {@link MarkedElementsManager} was set.
     */
    public void initMarkers() {
        if (getMarkedElementsManager() == null) {
            return;
        }

        for (String elementName : getMarkedElementsManager().getElementNames()) {
            addMarker(elementName);
        }
    }

    /**
     * shows the marker for the given element name. This only works if at least one of the {@link DataSet}s implements
     * the {@link MarkerXProvider} interface in order to determine the x-position of the marker.
     * 
     * @param elementName
     */
    private void addMarker(String elementName) {
        /*
         * we want to ensure, that only one indicator of the same name exists (important in the chart).
         */
        removeMarker(elementName);

        List<Double> xPositions = getMarkerXPositions(elementName);
        List<DataIndicator> dataIndicators = new ArrayList<DataIndicator>();
        for (Double xPos : xPositions) {
            DataIndicator indicator = createXIndicator(xPos, elementName);
            dataIndicators.add(indicator);
            addDecoration(indicator);
        }
        this.markerDataIndicators.put(elementName, dataIndicators);
        repaintChart();
    }

    /**
     * removes the marker for the given elementName both from the chart and from our map.
     * 
     * @param elementName the name of the element, for which to remove the marker.
     */
    private void removeMarker(String elementName) {
        List<DataIndicator> indicators = markerDataIndicators.get(elementName);
        if (indicators != null) {
            for (DataIndicator indicator : indicators) {
                removeDecoration(indicator);
            }
        }
        this.markerDataIndicators.remove(elementName);
        repaintChart();
    }

    /**
     * adds the inidicators for horizontal and vertical range to the chart.
     */
    private void createHVRangeIndicators() {
        removeHVRangeIndicators();
        List<Double> xPositions = getMarkerXPositions(MarkerXProvider.ELEMENT_NAME_HV_BORDER);
        Double hvBorder = null;
        if (xPositions.size() == 1) {
            hvBorder = xPositions.get(0);
        } else {
            return;
        }

        double minX = getXAxis().getVisibleRange().getMin();
        double maxX = getXAxis().getVisibleRange().getMax();
        hRangeIndicator = createRangeIndicator(new DataRange(minX, hvBorder), COLOR_H_RANGE, "H");
        addDecoration(hRangeIndicator);

        vRangeIndicator = createRangeIndicator(new DataRange(hvBorder, maxX), COLOR_V_RANGE, "V");

        addDecoration(vRangeIndicator);

        repaintChart();
    }

    private DataIndicator createRangeIndicator(DataRange dataRange, Color fillColor, String label) {
        DataIndicator rangeIndicator = new DataIndicator(DataIndicator.X_RANGE, dataRange, label);

        rangeIndicator.setTextLocation(0.8);
        rangeIndicator.setStyle(new Style(fillColor, fillColor));

        LabelRenderer labelRenderer = rangeIndicator.getLabelRenderer();
        labelRenderer.setBackground(null);
        labelRenderer.setForeground(fillColor.darker());
        labelRenderer.setFont(DEFAULT_FONT_RANGE);
        return rangeIndicator;
    }

    /**
     * removes the indicators for horizontal and vertical range from the chart.
     */
    private void removeHVRangeIndicators() {
        if (this.hRangeIndicator != null) {
            this.removeDecoration(hRangeIndicator);
        }
        if (this.vRangeIndicator != null) {
            this.removeDecoration(vRangeIndicator);
        }
        repaintChart();
    }

    /**
     * searches for the first markerXProvider DataSet, that returns a non-null xposition for the given element. This is
     * then returned.
     * 
     * @return the DataSet, if one is found. null otherwise.
     */
    private List<Double> getMarkerXPositions(String elementName) {

        /*
         * if a x-provider is explicitely set, we try to get the x-pos from there.
         */
        List<Double> xPositions = new ArrayList<Double>();
        if (getMarkerXProvider() != null) {
            xPositions = getMarkerXProvider().getXPositions(elementName);
        }

        if (xPositions.size() > 0) {
            return xPositions;
        }

        /*
         * if it is not set, or does not return a valid value, then we search in the datasets.
         */
        DataSource[] dataSources = getDataSources();
        for (int i = 0; i < dataSources.length; i++) {
            DataSet[] dataSets = dataSources[i].getDataSets();
            for (int j = 0; j < dataSets.length; j++) {
                DataSet dataSet = dataSets[j];
                if (dataSet instanceof MarkerXProvider) {
                    MarkerXProvider markable = (MarkerXProvider) dataSet;
                    List<Double> xPosNew = markable.getXPositions(elementName);
                    /*
                     * we take the longest list ...
                     */
                    if (xPosNew.size() > xPositions.size()) {
                        return xPositions = xPosNew;
                    }
                }
            }
        }
        /* if no dataset implementing MarkerXProvider, then we return null */
        return xPositions;
    }

    /**
     * This method creates a data-indicater of our default shape.
     * 
     * @return the DataIndicator object.
     */
    private DataIndicator createXIndicator(double xPos, String label) {
        DataIndicator pointIndicator = new DataIndicator(DataIndicator.X_VALUE, xPos, label);

        pointIndicator.setTextLocation(0.06);
        pointIndicator.setStyle(DEFAULT_INDICATOR_STYLE);

        LabelRenderer labelRenderer = pointIndicator.getLabelRenderer();
        labelRenderer.setHorizontalAlignment(SwingConstants.CENTER);
        labelRenderer.setBackground(Color.WHITE);
        labelRenderer.setForeground(Color.DARK_GRAY);
        labelRenderer.setBorder(new LineBorder(Color.DARK_GRAY));
        labelRenderer.setFont(DEFAULT_INDICATOR_FONT);

        return pointIndicator;
    }

    /**
     * @param markedElementsManager the markedElementsManager to set
     */
    public void setMarkedElementsManager(MarkedElementsManager markedElementsManager) {
        this.markedElementsManager = markedElementsManager;
        markedElementsManager.addListener(new MarkedElementsManagerListener() {

            @Override
            public void addedElementName(String elementName) {
                addMarker(elementName);
            }

            @Override
            public void removedElementName(String elementName) {
                removeMarker(elementName);
            }
        });
    }

    /**
     * @return the markedElementsManager
     */
    private MarkedElementsManager getMarkedElementsManager() {
        return markedElementsManager;
    }

    /**
     * @param markerXProvider the markerXProvider to set
     */
    public void setMarkerXProvider(MarkerXProvider markerXProvider) {
        this.markerXProvider = markerXProvider;
    }

    /**
     * @return the markerXProvider
     */
    public MarkerXProvider getMarkerXProvider() {
        return markerXProvider;
    }

    /**
     * @param visibleHVIndicators the visibleHVIndicators to set
     */
    public void setVisibleHVIndicators(boolean visibleHVIndicators) {
        this.visibleHVIndicators = visibleHVIndicators;
        if (visibleHVIndicators) {
            createHVRangeIndicators();
        } else {
            removeHVRangeIndicators();
        }
    }

    /**
     * @return the visibleHVIndicators
     */
    public boolean isVisibleHVIndicators() {
        return visibleHVIndicators;
    }
}