<filename>holoviews/plotting/plotly/chart3d.py
from __future__ import absolute_import, division, unicode_literals

import param
import numpy as np

from plotly import colors
from plotly.figure_factory._trisurf import trisurf as trisurface

from ...core.options import SkipRendering
from .element import ElementPlot, ColorbarPlot
from .chart import ScatterPlot, CurvePlot


class Chart3DPlot(ElementPlot):

    aspect = param.Parameter(default='cube')

    camera_angle = param.NumericTuple(default=(0.2, 0.5, 0.1, 0.2))

    camera_position = param.NumericTuple(default=(0.1, 0, -0.1))

    camera_zoom = param.Integer(default=3)

    projection = param.String(default='3d')

    width = param.Integer(default=500)

    height = param.Integer(default=500)

    zticks = param.Parameter(default=None, doc="""
        Ticks along z-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations.""")

    def get_data(self, element, ranges, style):
        return [dict(x=element.dimension_values(0),
                     y=element.dimension_values(1),
                     z=element.dimension_values(2))]


class SurfacePlot(Chart3DPlot, ColorbarPlot):

    trace_kwargs = {'type': 'surface'}

    style_opts = ['alpha', 'lighting', 'lightposition', 'cmap']

    def graph_options(self, element, ranges, style):
        opts = super(SurfacePlot, self).graph_options(element, ranges, style)
        copts = self.get_color_opts(element.vdims[0], element, ranges, style)
        return dict(opts, **copts)

    def get_data(self, element, ranges, style):
        return [dict(x=element.dimension_values(0, False),
                     y=element.dimension_values(1, False),
                     z=element.dimension_values(2, flat=False))]


class Scatter3DPlot(Chart3DPlot, ScatterPlot):

    trace_kwargs = {'type': 'scatter3d', 'mode': 'markers'}


class Path3DPlot(Chart3DPlot, CurvePlot):

    trace_kwargs = {'type': 'scatter3d', 'mode': 'lines'}

    _per_trace = True

    _nonvectorized_styles = []

    def graph_options(self, element, ranges, style):
        opts = super(Path3DPlot, self).graph_options(element, ranges, style)
        opts['line'].pop('showscale', None)
        return opts

    def get_data(self, element, ranges, style):
        return [dict(x=el.dimension_values(0), y=el.dimension_values(1),
                     z=el.dimension_values(2))
                for el in element.split()]


class TriSurfacePlot(Chart3DPlot, ColorbarPlot):

    style_opts = ['cmap', 'edges_color', 'facecolor']

    def get_data(self, element, ranges, style):
        try:
            from scipy.spatial import Delaunay
        except:
            SkipRendering("SciPy not available, cannot plot TriSurface")
        x, y, z = (element.dimension_values(i) for i in range(3))
        points2D = np.vstack([x, y]).T
        tri = Delaunay(points2D)
        simplices = tri.simplices
        return [dict(x=x, y=y, z=z, simplices=simplices)]

    def graph_options(self, element, ranges, style):
        opts = super(TriSurfacePlot, self).graph_options(element, ranges, style)
        copts = self.get_color_opts(element.dimensions()[2], element, ranges, style)
        opts['colormap'] = [tuple(v/255. for v in colors.hex_to_rgb(c))
                            for _, c in copts['colorscale']]
        opts['scale'] = [l for l, _ in copts['colorscale']]
        opts['show_colorbar'] = self.colorbar
        edges_color = style.get('edges_color', None)
        if edges_color:
            opts['edges_color'] = edges_color
            opts['plot_edges'] = True

        opts['colorbar'] = copts.get('colorbar', None)

        return {k: v for k, v in opts.items() if 'legend' not in k and k != 'name'}

    def init_graph(self, data, options, index=0):
        trisurf_kwargs = super(TriSurfacePlot, self).init_graph(
            data, options, index)[0]

        # Pop colorbar options since these aren't accepted by the trisurf
        # figure factory.
        colorbar = trisurf_kwargs.pop('colorbar', None)
        trisurface_traces = trisurface(**trisurf_kwargs)

        # Find colorbar to set colorbar options. Colorbar is associated with
        # a `scatter3d` scatter trace.
        if colorbar:
            marker_traces = [trace for trace in trisurface_traces
                             if trace.type == 'scatter3d' and
                             trace.mode == 'markers']
            if marker_traces:
                marker_traces[0].marker.colorbar = colorbar

        return [t.to_plotly_json() for t in trisurface_traces]
