/*
 * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved.
 */
package ch.psi.plot.jzy3d;

import ch.psi.plot.MatrixPlotBase;
import ch.psi.plot.MatrixPlotSeries;
import ch.psi.plot.utils.IO;
import ch.psi.plot.utils.MonitoredPanel;
import ch.psi.plot.utils.SwingUtils;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.SwingUtilities;
import org.jzy3d.chart.Chart;
import org.jzy3d.chart.Settings;
import org.jzy3d.colors.Color;
import org.jzy3d.colors.ColorMapper;
import org.jzy3d.colors.colormaps.ColorMapGrayscale;
import org.jzy3d.colors.colormaps.ColorMapRainbow;
import org.jzy3d.contour.DefaultContourColoringPolicy;
import org.jzy3d.contour.IContourColoringPolicy;
import org.jzy3d.contour.MapperContourPictureGenerator;
import org.jzy3d.maths.BoundingBox3d;
import org.jzy3d.maths.Coord3d;
import org.jzy3d.maths.Range;
import org.jzy3d.plot3d.builder.Builder;
import org.jzy3d.plot3d.builder.Mapper;
import org.jzy3d.plot3d.builder.concrete.OrthonormalGrid;
import org.jzy3d.plot3d.primitives.AbstractDrawable;
import org.jzy3d.plot3d.primitives.Point;
import org.jzy3d.plot3d.primitives.Polygon;
import org.jzy3d.plot3d.primitives.ScatterMultiColor;
import org.jzy3d.plot3d.primitives.Shape;
import org.jzy3d.plot3d.primitives.axes.ContourAxeBox;
import org.jzy3d.plot3d.rendering.legends.colorbars.AWTColorbarLegend;
import org.jzy3d.plot3d.rendering.view.modes.ViewBoundMode;

/**
 *
 */
public class MatrixPlot extends MatrixPlotBase {
    
    JPanel panel;
    private Chart chart;
    private Mapper mapper;
    private Shape surface;
    private MatrixPlotSeries series;
    private double[][] data;

    public MatrixPlot() {
        super();
        panel = new MonitoredPanel() {
            protected void onShown() {
                checkBounds(true);
            }
        };
        panel.setLayout(new BorderLayout());
        //panel.setLayout(new GridLayout(1,1));
        setColormap(Colormap.TEMPERATURE);        
        if (getHardwareAccelerated() != Settings.getInstance().isHardwareAccelerated()) {
            Settings.getInstance().setHardwareAccelerated(getHardwareAccelerated());
        }
        panel.setPreferredSize(new Dimension(640, 480));
        setupPopupMenu();
    }
    
    @Override
    protected Object onAddedSeries(final MatrixPlotSeries s) {
        if (!SwingUtilities.isEventDispatchThread()) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        onAddedSeries(s);
                    }
                });
            } catch (Exception ex) {
                Logger.getLogger(MatrixPlot.class.getName()).log(Level.SEVERE, null, ex);
            }
            return this;
        }

        synchronized (chartLock) {
            if (s != null) {
                if (chart != null) {
                    chart.clear();
                }
            }

            if (s == null) {
                //if (chart != null) {
                //chart.clear();
                //}
                series = null;
                data = null;
            } else {
                if (getAxis(AxisId.X).isAutoRange()) {
                    rangeX = new Range(s.getMinX(), s.getMaxX());
                } else {
                    rangeX = new Range(getAxis(AxisId.X).getMin(), getAxis(AxisId.X).getMax());
                }
                if (getAxis(AxisId.Y).isAutoRange()) {
                    rangeY = new Range(s.getMinY(), s.getMaxY());
                } else {
                    rangeY = new Range(getAxis(AxisId.Y).getMin(), getAxis(AxisId.Y).getMax());
                }
                //If same series & same dimensions then preserve data
                if ((data == null) || (series != s) || (s.getNumberOfBinsY() != data.length) || (s.getNumberOfBinsX() != data[0].length)) {
                    data = new double[s.getNumberOfBinsY()][s.getNumberOfBinsX()];
                    for (int i = 0; i < data.length; i++) {
                        Arrays.fill(data[i], Double.NaN);
                    }
                }
                series = s;
            }
        }
        createGraph();
        return this;
    }

    @Override
    protected void onAxisRangeChanged(AxisId axis_id) {
        if (series != null) {
            updateGraph(true);
        }
    }

    @Override
    protected void onRemovedSeries(MatrixPlotSeries s) {
        series = null;
        if (chart != null) {
            chart.clear();
        }
    }

    @Override
    protected void onAppendData(MatrixPlotSeries s, int indexX, int indexY, double x, double y, double z) {
        data[indexY][indexX] = z;
    }

    @Override
    protected void onSetData(MatrixPlotSeries s, double[][] data) {
        this.data = data;
    }

    @Override
    public void doUpdate() {
        updateGraph(false);
    }

    public void updateSeries(MatrixPlotSeries s) {
        updateGraph(false);
    }

    @Override
    public double[][] getSeriesData(MatrixPlotSeries s) {
        return data;
    }

    @Override
    protected JPanel createChartPanel() {
        return panel;
    }

    private Range rangeX;
    private Range rangeY;
    double[] rangeZ = null;

    boolean showLegend = true;

    public void setShowLegend(boolean value) {
        if (value != showLegend) {
            showLegend = value;
            if ((panel != null) && (panel.isShowing())) {
                updateGraph(true);
            }
        }
    }

    public boolean getShowLegend() {
        return showLegend;
    }

    boolean showFace = true;

    public void setShowFace(boolean value) {
        if (value != showFace) {
            showFace = value;
            if ((panel != null) && (panel.isShowing())) {
                updateGraph(true);
            }
        }
    }

    public boolean getShowFace() {
        return showFace;
    }

    boolean showFrame = false;

    public void setShowFrame(boolean value) {
        if (value != showFrame) {
            showFrame = value;
            if ((panel != null) && (panel.isShowing())) {
                updateGraph(true);
            }
        }
    }

    public boolean getShowFrame() {
        return showFrame;
    }

    boolean showAxis = true;

    public void setShowAxis(boolean value) {
        if (value != showAxis) {
            showAxis = value;
            if ((panel != null) && (panel.isShowing())) {
                updateGraph(true);
            }
        }
    }

    public boolean getShowAxis() {
        return showAxis;
    }

    public enum Colormap {

        GRAYSCALE,
        TEMPERATURE,
    }
    Colormap colormap = Colormap.TEMPERATURE;

    public void setColormap(Colormap value) {
        if (value != colormap) {
            colormap = value;
            if ((panel != null) && (panel.isShowing())) {
                updateGraph(true);
            }
        }
    }

    public Colormap getColormap() {
        return colormap;
    }

    public enum Quality {

        LOW,
        MEDIUM,
        HIGH,
        HIGHEST;

        private org.jzy3d.plot3d.rendering.canvas.Quality toJzy3dQuality() {
            switch (this) {
                case LOW:
                    return org.jzy3d.plot3d.rendering.canvas.Quality.Fastest;
                case MEDIUM:
                    return org.jzy3d.plot3d.rendering.canvas.Quality.Intermediate;
                case HIGH:
                    return org.jzy3d.plot3d.rendering.canvas.Quality.Advanced;
                case HIGHEST:
                    return org.jzy3d.plot3d.rendering.canvas.Quality.Nicest;
            }
            return null;
        }
    }
    Quality quality = Quality.HIGH;

    public void setQuality(Quality quality) {
        this.quality = quality;
        if (series != null) {
            createGraph();
        }
    }

    public Quality getQuality() {
        return quality;
    }

    public enum Mode {

        TOP,
        PROFILE,
        FREE;

        private org.jzy3d.plot3d.rendering.view.modes.ViewPositionMode toJzy3dMode() {
            switch (this) {
                case TOP:
                    return org.jzy3d.plot3d.rendering.view.modes.ViewPositionMode.TOP;
                case PROFILE:
                    return org.jzy3d.plot3d.rendering.view.modes.ViewPositionMode.PROFILE;
                case FREE:
                    return org.jzy3d.plot3d.rendering.view.modes.ViewPositionMode.FREE;
            }
            return null;
        }
    }
    Mode mode = Mode.FREE;

    public void setMode(Mode mode) {
        this.mode = mode;
        if (series != null) {
            updateGraph(true);
        }
    }

    public Mode getMode() {
        return mode;
    }

    public enum Contour {

        NONE,
        NORMAL,
        FILLED,
        HEIGHT_MAP,
        CONTOUR_3D

    }
    Contour contour = Contour.NONE;

    public void setContour(Contour contour) {
        this.contour = contour;
        if (series != null) {
            createGraph();
        }
    }

    public Contour getContour() {
        return contour;
    }

    int contourLevels = 10;

    public void setContourLevels(int value) {
        contourLevels = value;
        updateGraph(true);
    }

    public int getContourLevels() {
        return contourLevels;
    }

    int contourDensity = 400;

    public void setContourDensity(int value) {
        contourDensity = value;
        updateGraph(true);
    }

    public int getContourDensity() {
        return contourDensity;
    }

    java.awt.Color frameColor = java.awt.Color.BLACK;

    public void setFrameColor(java.awt.Color value) {
        frameColor = value;
        updateGraph(true);
    }

    public java.awt.Color getFrameColor() {
        return frameColor;
    }

    JPopupMenu menuPopup;

    void setupPopupMenu() {
        menuPopup = new JPopupMenu();
        JMenuItem menuUpdate = new JMenuItem("Update");
        final JCheckBoxMenuItem menuShowAxis = new JCheckBoxMenuItem("Show Axis");
        final JCheckBoxMenuItem menuShowLegend = new JCheckBoxMenuItem("Show Legend");
        final JCheckBoxMenuItem menuShowFace = new JCheckBoxMenuItem("Show Face");
        final JCheckBoxMenuItem menuShowFrame = new JCheckBoxMenuItem("Show Frame");
        JMenuItem frameColor = new JMenuItem("Set Frame Color");

        final JMenu menuColormap = new JMenu("Colormap");
        final JMenu menuMode = new JMenu("Mode");
        final JMenu menuContour = new JMenu("Contour");
        final JMenu menuQuality = new JMenu("Quality");
        menuPopup.add(menuShowAxis);
        menuPopup.add(menuShowLegend);
        menuPopup.add(menuShowFace);
        menuPopup.add(menuShowFrame);
        menuPopup.add(frameColor);
        menuPopup.add(menuColormap);
        menuPopup.add(menuMode);
        menuPopup.add(menuContour);
        menuPopup.add(menuQuality);
        menuPopup.add(menuUpdate);
        menuUpdate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    updateGraph(true);
                } catch (Exception ex) {
                }
            }
        });

        frameColor.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    String ret = SwingUtils.getString(panel, "Enter frame color (name or R,G,B):", getFrameColor().getRed() + "," + getFrameColor().getGreen() + "," + getFrameColor().getBlue());
                    if (ret != null) {
                        java.awt.Color color = null;
                        if (ret.contains(",")) {
                            String[] tokens = ret.split(",");
                            color = new java.awt.Color(Integer.valueOf(tokens[0]), Integer.valueOf(tokens[1]), Integer.valueOf(tokens[2]));
                        } else {
                            Field field = java.awt.Color.class.getField(ret);
                            color = (java.awt.Color) field.get(null);
                        }
                        setFrameColor(color);
                        updateGraph(true);
                    }
                } catch (Exception ex) {
                    SwingUtils.showException(panel, ex);
                }
            }
        });

        menuShowFrame.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setShowFrame(!getShowFrame());
                menuShowFrame.setSelected(getShowFrame());
            }
        });
        menuShowFrame.setSelected(getShowFrame());

        menuShowLegend.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setShowLegend(!getShowLegend());
                menuShowLegend.setSelected(getShowLegend());
            }
        });
        menuShowLegend.setSelected(getShowLegend());

        menuShowFace.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setShowFace(!getShowFace());
                menuShowLegend.setSelected(getShowFace());
            }
        });
        menuShowFace.setSelected(getShowFace());

        menuShowAxis.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setShowAxis(!getShowAxis());
                menuShowAxis.setSelected(getShowAxis());
            }
        });
        menuShowAxis.setSelected(getShowAxis());

        ButtonGroup colormapGroup = new ButtonGroup();
        for (Colormap c : Colormap.values()) {
            final JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(c.toString());
            colormapGroup.add(menuItem);
            menuItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setColormap(Colormap.valueOf(e.getActionCommand()));
                }
            });
            menuItem.setSelected(getColormap().toString().equals(menuItem.getText()));
            menuColormap.add(menuItem);
        }

        ButtonGroup modeGroup = new ButtonGroup();
        for (Mode q : Mode.values()) {
            final JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(q.toString());
            modeGroup.add(menuItem);
            menuItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setMode(Mode.valueOf(e.getActionCommand()));
                }
            });
            menuItem.setSelected(getMode().toString().equals(menuItem.getText()));
            menuMode.add(menuItem);
        }

        ButtonGroup contourGroup = new ButtonGroup();
        for (Contour q : Contour.values()) {
            final JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(q.toString());
            contourGroup.add(menuItem);
            menuItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setContour(Contour.valueOf(e.getActionCommand()));
                }
            });
            menuItem.setSelected(getContour().toString().equals(menuItem.getText()));
            menuContour.add(menuItem);
        }
        menuContour.addSeparator();
        JMenuItem levels = new JMenuItem("Set Levels");
        levels.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    String ret = SwingUtils.getString(panel, "Enter number of contour levels:", getContourLevels());
                    if (ret != null) {
                        Integer levels = Integer.valueOf(ret);
                        setContourLevels(levels);
                    }
                } catch (Exception ex) {
                    SwingUtils.showException(panel, ex);
                }
            }
        });
        menuContour.add(levels);

        JMenuItem density = new JMenuItem("Set Density");
        density.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    String ret = SwingUtils.getString(panel, "Enter contour density:", getContourDensity());
                    if (ret != null) {
                        Integer density = Integer.valueOf(ret);
                        setContourDensity(density);
                    }
                } catch (Exception ex) {
                    SwingUtils.showException(panel, ex);
                }
            }
        });
        menuContour.add(density);

        ButtonGroup qualityGroup = new ButtonGroup();
        for (Quality q : Quality.values()) {
            final JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(q.toString());
            qualityGroup.add(menuItem);
            menuItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setQuality(Quality.valueOf(e.getActionCommand()));
                }
            });
            menuItem.setSelected(getQuality().toString().equals(menuItem.getText()));
            menuQuality.add(menuItem);
        }
    }

    protected static void remap(Shape shape, Mapper mapper) {
        List<AbstractDrawable> polygons = shape.getDrawables();
        for (AbstractDrawable d : polygons) {
            if (d instanceof Polygon) {
                Polygon p = (Polygon) d;
                for (int i = 0; i < p.size(); i++) {
                    Point pt = p.get(i);
                    Coord3d c = pt.xyz;
                    c.z = (float) mapper.f(c.x, c.y);
                }
            }
        }
    }

    final Object chartLock = new Object();

    void createGraph() {     /*   
         if (!SwingUtilities.isEventDispatchThread()) {
         try {
         SwingUtilities.invokeAndWait(new Runnable() {
         @Override
         public void run() {
         createGraph();
         }
         });
         } catch (Exception ex) {
         Logger.getLogger(MatrixPlot.class.getName()).log(Level.SEVERE, null, ex);
         }
         }
         */

        synchronized (chartLock) {
            if (series != null) {
                if ((getContour() == Contour.NONE) || (getContour() == Contour.CONTOUR_3D)) {
                    chart = org.jzy3d.chart.factories.ContourChartComponentFactory.chart(getQuality().toJzy3dQuality(), "awt");
                } else {
                    chart = new org.jzy3d.chart.factories.ContourChartComponentFactory().newChart(getQuality().toJzy3dQuality(), "awt");
                }

                if (rangeY == null) {
                    rangeY = new Range(0, data.length - 1);
                }
                if (rangeX == null) {
                    rangeX = new Range(0, data[0].length - 1);
                }
                mapper = new Mapper() {
                    @Override
                    public double f(double x, double y) {
                        if ((data == null)||(data.length==0)) {
                            return Double.NaN;
                        }
                        int indexY=(int) Math.round((y - rangeY.getMin()) / ((rangeY.getMax() - rangeY.getMin()) / (data.length - 1)));
                        int indexX=(int) Math.round((x - rangeX.getMin()) / ((rangeX.getMax() - rangeX.getMin()) / (data[0].length - 1)));
                        double ret = data[indexY][indexX];                        
                        if (Double.isInfinite(ret))
                            ret=Double.NaN;
                        return ret;

                    }
                };
                Component canvas = (Component) chart.getCanvas();
                panel.removeAll();
                panel.add(canvas);

                //Todo: why it is not displyed if I don'r pack the Window?
                if (panel.isVisible()) {
                    panel.validate();
                }

                chart.addMouseController();
                chart.getAxeLayout().setXAxeLabel(getAxis(AxisId.X).getLabel());
                chart.getAxeLayout().setYAxeLabel(getAxis(AxisId.Y).getLabel());
                chart.getAxeLayout().setZAxeLabel(getAxis(AxisId.Z).getLabel());

                chart.getCanvas().addMouseController(new MouseAdapter() {
                    @Override
                    public void mouseReleased(MouseEvent e) {
                        checkPopup(e);
                    }

                    @Override
                    public void mousePressed(MouseEvent e) {
                        checkPopup(e);
                    }

                    private void checkPopup(MouseEvent e) {
                        if (e.isPopupTrigger()) {
                            menuPopup.show(e.getComponent(), e.getX(), e.getY());
                        }
                    }
                });
            }

            updateGraph(true);
        }

    }

    private void updateGraph(final boolean newSeries) {
        if (!SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    updateGraph(newSeries);
                }
            });
            return;
        }
//long start = System.currentTimeMillis();        
        synchronized (chartLock) {
            Shape former = surface;
            if (series == null) {
                if (surface != null) {
                    chart.getScene().getGraph().remove(former, false);
                }
                surface = null;
            } else {

                //TODO: Not succeding having the partial updates - and they are not performing much better neither
                boolean changedBoundsZ = checkBounds(false);
                if ((surface == null) || (newSeries) || getAxis(AxisId.Z).isAutoRange() || changedBoundsZ) {
                    //if (true) {
                    surface = (Shape) Builder.buildOrthonormal(
                            new OrthonormalGrid(rangeX, series.getNumberOfBinsX(), rangeY, series.getNumberOfBinsY()), mapper);
                    ColorMapper colorMapper = new ColorMapper((colormap == Colormap.TEMPERATURE) ? new ColorMapRainbow() : new ColorMapGrayscale(),
                            (float) ((getAxis(AxisId.Z).isAutoRange()) ? surface.getBounds().getZmin() : getAxis(AxisId.Z).getMin()),
                            (float) ((getAxis(AxisId.Z).isAutoRange()) ? surface.getBounds().getZmax() : getAxis(AxisId.Z).getMax()),
                            new Color(1, 1, 1, .5f));
                    surface.setColorMapper(colorMapper);
                    surface.setFaceDisplayed(true);
                    surface.setWireframeDisplayed(getShowFrame());

                    surface.setWireframeColor(new Color(frameColor.getRed(), frameColor.getGreen(), frameColor.getBlue(), frameColor.getAlpha()));
                    surface.setFaceDisplayed(getShowFace());
                    if (getShowLegend()) {
                        AWTColorbarLegend cbar = new AWTColorbarLegend(surface, chart.getView().getAxe().getLayout());
                        surface.setLegend(cbar);
                    }

                    if (getContour() != Contour.NONE) {
                        int xRes = getContourDensity();
                        int yRes = getContourDensity();

                        MapperContourPictureGenerator contour = new MapperContourPictureGenerator(mapper, rangeX, rangeY);
                        IContourColoringPolicy policy = new DefaultContourColoringPolicy(colorMapper);
                        ContourAxeBox cab = (getContour() == Contour.CONTOUR_3D) ? null : (ContourAxeBox) chart.getView().getAxe();

                        switch (getContour()) {
                            case NORMAL:
                                cab.setContourImg(contour.getContourImage(policy, xRes, yRes, getContourLevels()), rangeX, rangeY);
                                break;
                            case FILLED:
                                cab.setContourImg(contour.getFilledContourImage(policy, xRes, yRes, getContourLevels()), rangeX, rangeY);
                                break;
                            case HEIGHT_MAP:
                                cab.setContourImg(contour.getHeightMap(policy, xRes, yRes, getContourLevels()), rangeX, rangeY);
                                break;
                            case CONTOUR_3D:
                                //int nx=series.getNumberOfBinsX();
                                //int ny=series.getNumberOfBinsY();

                                float dx = ((float) series.getNumberOfBinsX()) / xRes;
                                float dy = ((float) series.getNumberOfBinsY()) / yRes;
                                double[][] contours = contour.getContourMatrix(xRes, yRes, getContourLevels());
                                // Create the dot cloud scene and fill with data
                                int size = xRes * yRes;
                                Coord3d[] points = new Coord3d[size];
                                for (int x = 0; x < xRes; x++) {
                                    for (int y = 0; y < yRes; y++) {
                                        float px = (float) ((float) x * dx * series.getBinWidthX() + series.getMinX());
                                        float py = (float) ((float) y * dy * series.getBinWidthY() + series.getMinY());

                                        if (contours[x][y] > -Double.MAX_VALUE) { // Non contours points are -Double.MAX_VALUE and are not painted
                                            points[x * yRes + y] = new Coord3d(px, py, (float) contours[x][y]);
                                        } else {
                                            points[x * yRes + y] = new Coord3d(px, py, Float.NaN);
                                      //points[x*ny+y] = new Coord3d((float)x * series.getBinWidthX()+ series.getMinX(),(float)y* series.getBinWidthY()+ series.getMinY(),(float)0.0);                    

                                            //points[x*ny+y] = new Coord3d((float)x * series.getBinWidthX()+ series.getMinX(),(float)y* series.getBinWidthY()+ series.getMinY(),(float)mapper.f((float)x * series.getBinWidthX()+ series.getMinX(), (float)y* series.getBinWidthY()+ series.getMinY()));        
                                        }
                                    }
                                }
                                //ScatterMultiColor scatter = new ScatterMultiColor( points, new ColorMapper( new ColorMapRainbow(), -600.0f, 600.0f ) );
                                ScatterMultiColor scatter = new ScatterMultiColor(points, colorMapper);
                                surface.add(scatter);
                                //chart.getScene().add(scatter);
                                break;
                        }
                    }
                    chart.setViewMode(getMode().toJzy3dMode());
                    chart.setAxeDisplayed(getShowAxis());
                }

                if (surface == former) {
                    remap(surface, mapper);
                    chart.render();
                } else {

                    chart.getScene().getGraph().add(surface, false);
                    if (former != null) {
                        chart.getScene().getGraph().remove(former, false);
                    }
                    boolean changed = checkBounds(true);
                    //if (!changed)
                    //    chart.getView().updateBounds();

                }
            }
//System.out.println(System.currentTimeMillis()-start);        
        }
    }

    protected boolean checkBounds(boolean updateBounds) {
        if (chart == null) {
            return false;
        }
        boolean changed = false;
        //If manual bounds
        if (!getAxis(AxisId.X).isAutoRange() || !getAxis(AxisId.Y).isAutoRange() || !getAxis(AxisId.Z).isAutoRange()) {
            //Deferring setting bounds untiul the panel is displayed
            if (panel.isShowing() && (chart != null)) {
                if (chart.getView().getBoundsMode() != ViewBoundMode.MANUAL) {
                    changed = true;
                }
                BoundingBox3d bounds = chart.getView().getBounds();
                if (!getAxis(AxisId.X).isAutoRange()) {
                    if (bounds.getXmin() != getAxis(AxisId.X).getMin()) {
                        bounds.setXmin((float) getAxis(AxisId.X).getMin());
                        changed = true;
                    }
                    if (bounds.getXmax() != getAxis(AxisId.X).getMax()) {
                        bounds.setXmax((float) getAxis(AxisId.X).getMax());
                        changed = true;
                    }
                }
                if (!getAxis(AxisId.Y).isAutoRange()) {
                    if (bounds.getYmin() != getAxis(AxisId.Y).getMin()) {
                        bounds.setYmin((float) getAxis(AxisId.Y).getMin());
                        changed = true;
                    }
                    if (bounds.getYmax() != getAxis(AxisId.Y).getMax()) {
                        bounds.setYmax((float) getAxis(AxisId.Y).getMax());
                        changed = true;
                    }
                }
                if (!getAxis(AxisId.Z).isAutoRange()) {
                    if (bounds.getZmin() != getAxis(AxisId.Z).getMin()) {
                        bounds.setZmin((float) getAxis(AxisId.Z).getMin());
                        changed = true;
                    }
                    if (bounds.getZmax() != getAxis(AxisId.Z).getMax()) {
                        bounds.setZmax((float) getAxis(AxisId.Z).getMax());
                        changed = true;
                    }
                }
                if (changed && updateBounds) {
                    chart.getView().setBoundManual(bounds);
                }
            }
        } else if (chart.getView().getBoundsMode() != ViewBoundMode.AUTO_FIT) {
            if (updateBounds) {
                chart.getView().setBoundMode(ViewBoundMode.AUTO_FIT);
            }
            changed = true;
        }

        return changed;
    }

    protected void addPopupMenuItem(final JMenuItem item) {
        if (item == null) {
            menuPopup.addSeparator();
        } else {
            menuPopup.add(item);
        }
    }
        
    @Override
    public BufferedImage getSnapshot() {
                
        File temp=null;
        try{                        
            temp = File.createTempFile("snapshot", ".bmp");
            chart.screenshot(temp);
            return IO.readImageFromFile(temp.getAbsolutePath());
        }
        catch (Exception ex){
            return null;
        }
        finally{
            if (temp!=null)
                temp.delete();                    
        }
    }
    
    public void saveSnapshot(String filename, String format) throws IOException {
         if (chart!=null){
            chart.screenshot(new File(filename));
         }
    }   
    
}
