import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State, MATCH, ALL
from math import log10
import numpy as np
from profit.util import load
from profit.sur import Surrogate
from matplotlib import cm as colormaps
from matplotlib.colors import to_hex as color2hex

def init_app(config):
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    server = app.server
    app.config.suppress_callback_exceptions = False

    indata = load(config['files']['input']).flatten()
    outdata = load(config['files']['output']).flatten()

    # data = pd.concat([indata, outdata], 1) # TODO: data in table

    invars = indata.dtype.names
    outvars = outdata.dtype.names
    dd_opts_in = [{'label': invar, 'value': invar} for invar in invars]
    dd_opts_out = [{'label': outvar, 'value': outvar} for outvar in outvars]

    col_width = 400
    txt_width = 100
    dd_width = 250
    log_width = 50
    graph_height = 620
    txt_check_width = 50
    check_txt_width = txt_width-txt_check_width
    ax_opt_tit_sty = {'width': col_width}
    ax_opt_txt_sty = {'width': txt_width}
    ax_opt_log_sty = {'width': log_width}
    dd_sty = {'width': dd_width}
    axis_options_div_style = {'display': 'flex', 'align-items': 'center', 'height':36, 'padding': 1}
    fit_opt_txt_sty = {'width': txt_width}
    headline_sty = {'text-align': 'center', 'display': 'block', 'width': col_width-25}
    input_div_sty = {'height': 40}
    input_sty = {'width': 125}

    # try to load model with 'save' and 'fit' config option
    path = config['fit']['save'] or config['fit']['load']
    try:
        sur = Surrogate.load_model(path)
    except (TypeError, FileNotFoundError):
            print('Model could not be loaded')

    app.layout = html.Div(children=[
        html.Table(children=[html.Tr(children=[
            html.Td(id='axis-options', style={'width': '20%'}, children=[
                html.Div(dcc.RadioItems(
                    id='graph-type',
                    options=[{'label': i, 'value': i} for i in ['1D', '2D', '2D contour', '3D']],
                    value='1D',
                    labelStyle={'display': 'inline-block'})),
                html.Div(id='header-opt', children=[html.B("Axis options:", style=headline_sty)], style=ax_opt_tit_sty),
                html.Div(id='invar-1-div', style=axis_options_div_style, children=[
                    html.B('x: ', style=ax_opt_txt_sty),
                    dcc.Dropdown(id='invar', options=dd_opts_in, value=invars[0], style=dd_sty),
                    dcc.Checklist(id='invar-1-log', options=[{'label': 'log', 'value': 'log'}], style=ax_opt_log_sty),
                ]),
                html.Div(id='invar-2-div', style=axis_options_div_style, children=[
                    html.B('y: ', style=ax_opt_txt_sty),
                    dcc.Dropdown(
                        id='invar_2',
                        options=dd_opts_in,
                        value=invars[1] if len(invars) > 1 else invars[0],
                        style=dd_sty, ),
                    dcc.Checklist(
                        id='invar-2-log',
                        options=[{'label': 'log', 'value': 'log'}],
                        style=ax_opt_log_sty, ),
                ]),
                html.Div(id='invar-3-div', style=axis_options_div_style, children=[
                    html.B('z: ', style=ax_opt_txt_sty),
                    dcc.Dropdown(
                        id='invar_3',
                        options=dd_opts_in,
                        value=invars[2] if len(invars) > 2 else invars[0],
                        style=dd_sty,
                    ),
                    dcc.Checklist(
                        id='invar-3-log',
                        options=[{'label': 'log', 'value': 'log'}],
                        style=ax_opt_log_sty,
                    ),
                ]),
                html.Div(id='outvar-div', style=axis_options_div_style, children=[
                    html.B('output: ', style=ax_opt_txt_sty),
                    dcc.Dropdown(
                        id='outvar',
                        options=dd_opts_out,
                        value=outvars[0],
                        style=dd_sty, ),
                    dcc.Checklist(
                        id='outvar-log',
                        options=[{'label': 'log', 'value': 'log'}],
                        style=ax_opt_log_sty, ),
                ]),
                html.Div(id='color-div', style=axis_options_div_style, children=[
                    html.B("color: ", style={'width': txt_check_width}),
                    dcc.Checklist(
                        id='color-use',
                        options=[{'label': '', 'value': 'true'}],
                        style={'width': check_txt_width},
                        value=['true'], ),
                    dcc.Dropdown(
                        id='color-dropdown',
                        options=[{'label': 'OUTPUT', 'value': 'OUTPUT'}] + dd_opts_in + dd_opts_out,
                        value='OUTPUT',
                        style=dd_sty, ),
                ]),
                html.Div(id='error-div', style=axis_options_div_style, children=[
                    html.B("error: ", style={'width': txt_check_width}),
                    dcc.Checklist(
                        id='error-use',
                        options=[{'label': '', 'value': 'true'}],
                        style={'width': check_txt_width}, ),
                    dcc.Dropdown(
                        id='error-dropdown',
                        options=dd_opts_out,
                        value=outvars[-1],
                        style=dd_sty, ),
                ]),
                html.Div(id='fit-opt', children=html.B("Fit options:", style=headline_sty), style=ax_opt_tit_sty),
                html.Div(id='fit-use-div', style=axis_options_div_style, children=[
                    html.B("display fit:", style=fit_opt_txt_sty),
                    dcc.Checklist(
                        id='fit-use',
                        options=[{'label': '', 'value': 'show'}],
                        labelStyle={'display': 'inline-block'}, ),
                ]),
                html.Div(id='fit-multiinput-div', style=axis_options_div_style, children=[
                    html.B("multi-fit:", style=fit_opt_txt_sty),
                    dcc.Dropdown(
                        id='fit-multiinput-dropdown',
                        options=dd_opts_in,
                        value=invars[-1],
                        style=dd_sty, ),
                ]),
                html.Div(id='fit-number-div', style=axis_options_div_style, children=[
                    html.B("#fits:", style=fit_opt_txt_sty),
                    dcc.Input(id='fit-number', type='number', value=1, min=1),
                ]),
                html.Div(id='fit-conf-div', style=axis_options_div_style, children=[
                    html.B("\u03c3-confidence:", style=fit_opt_txt_sty),
                    dcc.Input(id='fit-conf', type='number', value=2, min=0),
                ]),
                html.Div(id='fit-noise-div', style=axis_options_div_style, children=[
                    dcc.Checklist(
                        id='fit-var',
                        options=[{'label': 'add noise covariance', 'value': 'add'}],
                        style={'margin-left': txt_width},
                    )
                ]),
                html.Div(id='fit-color-div', style=axis_options_div_style, children=[
                    html.B("fit-color:", style=fit_opt_txt_sty),
                    dcc.RadioItems(
                        id='fit-color',
                        options=[{'label': 'output', 'value': 'output'},
                                 {'label': 'multi-fit', 'value': 'multi-fit'},
                                 {'label': 'marker-color', 'value': 'marker-color'}],
                        value='output',
                        labelStyle={'display': 'inline-block'}, ),
                ]),
                html.Div(id='fit-opacity-div', style=axis_options_div_style, children=[
                    html.B("fit-opacity:", style=fit_opt_txt_sty),
                    html.Div(style={'width': col_width-txt_width}, children=[
                        dcc.Slider(
                            id='fit-opacity',
                            min=0,
                            max=1,
                            step=0.1,
                            value=0.5,
                            marks={i: {'label': f'{100 * i:.0f}%'} for i in [0, 0.2, 0.4, 0.6, 0.8, 1]},
                        ),
                    ]),
                ]),
                html.Div(id='fit-sampling-div', style=axis_options_div_style, children=[
                    html.B("#points:", style=fit_opt_txt_sty),
                    dcc.Input(id='fit-sampling', type='number', value=50, min=1, debounce=True, style={'appearance': 'textfield'}),
                ]),
            ]),
            html.Td(id='graph', style={'width': '80%'}, children=[html.Div(dcc.Graph(id='graph1'))]),
        ])]),
        html.Div(html.Table(id='filters', children=[html.Tr([
            html.Td(html.Div([
                dcc.Dropdown(
                    id='filter-dropdown',
                    options=dd_opts_in,
                    value=invars[0],
                    style={'width': 200, 'margin-right': 10},
                ),
                html.Button("Add Filter", id='add-filter', n_clicks=0),
            ], style={'display': 'flex'})),
            html.Td(html.Button("Clear Filter", id='clear-filter', n_clicks=0)),
            html.Td(html.Button("Clear all Filter", id='clear-all-filter', n_clicks=0)),
            html.Td(dcc.Slider(id='scale-slider',
                               min=-0.5, max=0.5,
                               value=0, step=0.01,
                               marks={i: f'{100*i:.0f}%' for i in [-0.5, -0.25, 0, 0.25, 0.5]},
                               ),
                    style={'width': 500}
            ),
            html.Td(html.Button("Scale Filter span", id='scale', n_clicks=0)),
        ])])),
        html.Div(html.Table(id='param-table', children=[
            html.Thead(id='param-table-head', children=[
                html.Tr(children=[
                    html.Th("Parameter", style=input_sty),
                    html.Th("log"),
                    html.Th("Slider", style={'width': 300}),
                    html.Th("Range (min/max)"),
                    html.Th("center/span"),
                    html.Th("filter active"),
                    html.Th("#digits"),
                    html.Th("reset"),
                ]),
            ]),
            html.Tbody(id='param-table-body', children=[
                html.Tr(children=[
                    html.Td(html.Div(id='param-text-div', children=[])),
                    html.Td(html.Div(id='param-log-div', children=[])),
                    html.Td(html.Div(id='param-slider-div', children=[])),
                    html.Td(html.Div(id='param-range-div', children=[])),
                    html.Td(html.Div(id='param-center-div', children=[])),
                    html.Td(html.Div(id='param-active-div', children=[])),
                    html.Td(html.Div(id='param-digits-div', children=[])),
                    html.Td(html.Div(id='param-reset-div', children=[])),
                ]),
            ]),
        ])),
    ])


    @app.callback(
        [Output('param-text-div', 'children'),
         Output('param-log-div', 'children'),
         Output('param-slider-div', 'children'),
         Output('param-range-div', 'children'),
         Output('param-center-div', 'children'),
         Output('param-active-div', 'children'),
         Output('param-digits-div', 'children'),
         Output('param-reset-div', 'children'), ],
        [Input('add-filter', 'n_clicks'),
         Input('clear-filter', 'n_clicks'),
         Input('clear-all-filter', 'n_clicks')],
        [State('filter-dropdown', 'value'),
         State('param-text-div', 'children'),
         State('param-log-div', 'children'),
         State('param-slider-div', 'children'),
         State('param-range-div', 'children'),
         State('param-center-div', 'children'),
         State('param-active-div', 'children'),
         State('param-digits-div', 'children'),
         State('param-reset-div', 'children')],
    )
    def add_filterrow(n_clicks, clear, clear_all, filter_dd, text, log, slider, range_div, center_div, active_div, dig_div, reset_div):
        ctx = dash.callback_context
        trigger_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if trigger_id == 'clear-all-filter':
            return [], [], [], [], [], [], [], []
        elif trigger_id == 'clear-filter':
            for i in range(len(text)):
                if text[i]['props']['children'][0] == filter_dd:
                    text.pop(i)
                    log.pop(i)
                    slider.pop(i)
                    range_div.pop(i)
                    center_div.pop(i)
                    active_div.pop(i)
                    dig_div.pop(i)
                    reset_div.pop(i)
                    break
        elif trigger_id == 'add-filter':
            for i in range(len(text)):
                if text[i]['props']['children'][0] == filter_dd:
                    return text, log, slider, range_div, center_div, active_div, dig_div, reset_div
            ind = invars.index(filter_dd)
            txt = filter_dd
            new_text = html.Div(id={'type': 'dyn-text', 'index': ind}, children=[txt], style={**input_div_sty, **input_sty})
            new_log = html.Div(id={'type': 'dyn-log', 'index': ind}, style=input_div_sty, children=[
                dcc.Checklist(id={'type': 'param-log', 'index': ind}, options=[{'label': '', 'value': 'log'}])], )
            new_slider = html.Div(id={'type': 'dyn-slider', 'index': ind}, style=input_div_sty, children=[
                create_slider(txt)], )
            new_range = html.Div(id={'type': 'dyn-range', 'index': ind}, style=input_div_sty, children=[
                dcc.Input(id={'type': 'param-range-min', 'index': ind}, type='number', debounce=True, style={**input_sty, 'appearance': 'textfield'}),
                dcc.Input(id={'type': 'param-range-max', 'index': ind}, type='number', debounce=True, style={**input_sty, 'appearance': 'textfield'}),
            ], )
            new_center = html.Div(id={'type': 'dyn-center', 'index': ind}, style=input_div_sty, children=[
                dcc.Input(id={'type': 'param-center', 'index': ind}, type='number', debounce=True, style={**input_sty, 'appearance': 'textfield'}),
                dcc.Input(id={'type': 'param-span', 'index': ind}, type='number', debounce=True, style={**input_sty, 'appearance': 'textfield'}),
            ], )
            new_active = html.Div(id={'type': 'dyn-active', 'index': ind},
                                  style={**input_div_sty, 'text-align': 'center'},
                                  children=[
                                      dcc.Checklist(id={'type': 'param-active', 'index': ind},
                                                    options=[{'label': '', 'value': 'act'}],
                                                    value=['act'], )
                                  ])
            new_dig = html.Div(id={'type': 'dyn-dig', 'index': ind}, children=[
                dcc.Input(id={'type': 'param-dig', 'index': ind}, type='number', value=5, min=0, style={'width':100})
            ])
            new_reset = html.Div(id={'type': 'dyn-reset', 'index': ind}, children=[
                html.Button("reset", id={'type': 'param-reset', 'index': ind}, n_clicks=0)
            ])
            text.append(new_text)
            log.append(new_log)
            slider.append(new_slider)
            range_div.append(new_range)
            center_div.append(new_center)
            active_div.append(new_active)
            dig_div.append(new_dig)
            reset_div.append(new_reset)
        return text, log, slider, range_div, center_div, active_div, dig_div, reset_div


    @app.callback(
        [Output({'type': 'param-range-min', 'index': MATCH}, 'step'),
         Output({'type': 'param-range-max', 'index': MATCH}, 'step'),
         Output({'type': 'param-center', 'index': MATCH}, 'step'),
         Output({'type': 'param-span', 'index': MATCH}, 'step'),
         Output({'type': 'param-slider', 'index': MATCH}, 'step'), ],
        Input({'type': 'param-dig', 'index': MATCH}, 'value')
    )
    def update_step(dig):
        """ Function to update an synchronise step-sizes throughout the filter-table.

        Args:
            dig (int): Number of digits to be used. Selected by the user via a 'dcc.Input'-layout-element.

        Returns:
            step: Step-size for the 4 'dcc.Input'-Elements and the slider step.

        """
        step = 10**(-dig)
        return step, step, step, step, step


    @app.callback(
        [Output({'type': 'param-range-min', 'index': MATCH}, 'value'),
         Output({'type': 'param-range-max', 'index': MATCH}, 'value'),
         Output({'type': 'param-slider', 'index': MATCH}, 'value'),
         Output({'type': 'param-slider', 'index': MATCH}, 'min'),
         Output({'type': 'param-slider', 'index': MATCH}, 'max'),
         Output({'type': 'param-center', 'index': MATCH}, 'value'),
         Output({'type': 'param-span', 'index': MATCH}, 'value'),
         Output({'type': 'param-slider', 'index': MATCH}, 'marks'), ],
        [Input('param-text-div', 'children'),
         Input({'type': 'param-log', 'index': MATCH}, 'value'),
         Input({'type': 'param-range-min', 'index': MATCH}, 'value'),
         Input({'type': 'param-range-max', 'index': MATCH}, 'value'),
         Input({'type': 'param-slider', 'index': MATCH}, 'value'),
         Input({'type': 'param-center', 'index': MATCH}, 'value'),
         Input({'type': 'param-span', 'index': MATCH}, 'value'),
         Input('scale', 'n_clicks'),
         Input({'type': 'param-dig', 'index': MATCH}, 'value'),
         Input({'type': 'param-reset', 'index': MATCH}, 'n_clicks'), ],
        [State({'type': 'param-slider', 'index': MATCH}, 'id'),
         State('scale-slider', 'value'),
         State({'type': 'param-slider', 'index': MATCH}, 'marks'), ]
    )
    def update_dyn_slider_range(text_div, log_act, dyn_min, dyn_max, slider_val, center, span, scale, dig, reset, id, scale_slider, marks):
        ctx = dash.callback_context
        try:
            trigger_id = ctx.triggered[0]["prop_id"].split('}')[0].split(',')[1].split(':')[1]
        except IndexError:
            trigger_id = ctx.triggered[0]["prop_id"]

        mark_lim = [float(i) for i in list(marks.keys())]

        data_in = indata[invars[id['index']]]
        data_min_0 = min(data_in[data_in > 0])

        if trigger_id == '"param-log"' and log_act != ['log']:
            dyn_min = 10**dyn_min
            dyn_max = 10**dyn_max
            slider_val = [10**val for val in slider_val]
            mark_lim = [10 ** lim for lim in mark_lim]
            if min(data_in) < 0 and slider_val[0] > 0:
                mark_lim[0] = min(data_in)
            span = (slider_val[1] - slider_val[0])/2
            center = (slider_val[0] + slider_val[1])/2

        if trigger_id != '"param-log"' and log_act == ['log']:
            dyn_min = 10 ** dyn_min
            dyn_max = 10 ** dyn_max
            slider_val = [10 ** val for val in slider_val]
            mark_lim = [10 ** lim for lim in mark_lim]
            if min(data_in) < 0 and slider_val[0] > 0:
                mark_lim[0] = min(data_in)

        if trigger_id == '"param-reset"':
            slider_val = [min(data_in), max(data_in)]

        if ctx.triggered[0]["prop_id"] == "scale.n_clicks":
            # print('scale')
            span = span * (1 + scale_slider)
            if log_act == ['log']:
                trigger_id = '"param-span"'
            else:
                dyn_min = center - span
                dyn_max = center + span
                slider_val = [dyn_min, dyn_max]

        if trigger_id == '"param-center"' or trigger_id == '"param-span"' and (center and span):
            # print('center')
            if log_act == ['log']:
                dyn_min = 10**(center-span)
                dyn_max = 10**(center+span)
            else:
                dyn_min = center - span
                dyn_max = center + span
            slider_val = [dyn_min, dyn_max]
        elif (trigger_id == '"param-range-min"' or trigger_id == '"param-range-max"') and (
                dyn_min is not None and dyn_max is not None):
            # print('range')
            slider_val = [dyn_min, dyn_max]
            span = (slider_val[1] - slider_val[0]) / 2
            center = (slider_val[0] + slider_val[1]) / 2
        elif slider_val:
            # print('slider')
            dyn_min = slider_val[0]
            dyn_max = slider_val[1]
            span = (slider_val[1] - slider_val[0]) / 2
            center = (slider_val[0] + slider_val[1]) / 2

        if log_act == ['log']:
            # log values
            try:
                log_dyn_min = log10(dyn_min)
            except ValueError:
                log_dyn_min = log10(data_min_0)
            log_dyn_max = log10(dyn_max)
            log_slider_val = [log_dyn_min, log_dyn_max]
            try:
                log_mark_lim = [log10(mark) for mark in mark_lim]
            except ValueError:
                log_mark_lim = [log10(data_min_0), log10(mark_lim[1])]
            log_span = (log_slider_val[1] - log_slider_val[0])/2
            log_center = log_slider_val[0] + log_span
            log_marks = {log_mark_lim[0]: str(round(log_mark_lim[0], dig)),
                         log_mark_lim[1]: str(round(log_mark_lim[1], dig))}
            return round(log_dyn_min, dig), round(log_dyn_max, dig), log_slider_val, log_mark_lim[0], log_mark_lim[1],\
                   round(log_center, dig), round(log_span, dig), log_marks
        else:
            marks = {mark_lim[0]: str(round(mark_lim[0], dig)), mark_lim[1]: str(round(mark_lim[1], dig))}
            return round(dyn_min, dig), round(dyn_max, dig), slider_val, mark_lim[0], mark_lim[1], \
                   round(center, dig), round(span, dig), marks


    @app.callback(
        [Output('invar-2-div', 'style'),
         Output('invar-3-div', 'style'),
         Output('color-div', 'style'),
         Output('error-div', 'style'),
         Output('fit-use-div', 'style'),
         Output('fit-multiinput-div', 'style'),
         Output('fit-number-div', 'style'),
         Output('fit-number', 'value'),
         Output('fit-conf-div', 'style'),
         Output('fit-noise-div', 'style'),
         Output('fit-color-div', 'style'),
         Output('fit-opacity-div', 'style'), ],
        [Input('graph-type', 'value'), ],
        [State('fit-number', 'value'), ]
    )
    def div_visibility(graph_type, fits):
        hide = axis_options_div_style.copy()
        hide['visibility'] = 'hidden'
        show = axis_options_div_style.copy()
        show['visibility'] = 'visible'
        if graph_type == '1D':
            return hide, hide, show, show, show, show, show, fits, show, show, hide, show
        if graph_type == '2D':
            if len(invars) <= 2:
                return show, hide, show, show, show, hide, hide, 1, show, show, show, show
            else:
                return show, hide, show, show, show, show, show, fits, show, show, show, show
        if graph_type == '2D contour':
            return show, hide, show, hide, hide, hide, hide, fits, hide, hide, hide, hide
        if graph_type == '3D':
            return show, show, hide, hide, show, hide, show, fits, hide, hide, hide, show
        else:
            return show, show, show, show, show, show, show, fits, show, show, show, show


    @app.callback(
        Output('graph1', 'figure'),
        [Input('invar', 'value'),
         Input('invar_2', 'value'),
         Input('invar_3', 'value'),
         Input('outvar', 'value'),
         Input('invar-1-log', 'value'),
         Input('invar-2-log', 'value'),
         Input('invar-3-log', 'value'),
         Input('outvar-log', 'value'),
         Input({'type': 'param-slider', 'index': ALL}, 'value'),
         Input('graph-type', 'value'),
         Input('color-use', 'value'),
         Input('color-dropdown', 'value'),
         Input('error-use', 'value'),
         Input('error-dropdown', 'value'),
         Input({'type': 'param-active', 'index': ALL}, 'value'),
         Input('fit-use', 'value'),
         Input('fit-multiinput-dropdown', 'value'),
         Input('fit-number', 'value'),
         Input('fit-conf', 'value'),
         Input('fit-var', 'value'),
         Input('fit-color', 'value'),
         Input('fit-opacity', 'value'),
         Input('fit-sampling', 'value'), ],
        [State({'type': 'param-slider', 'index': ALL}, 'id'),
         State({'type': 'param-center', 'index': ALL}, 'value'),
         State({'type': 'param-log', 'index': ALL}, 'value')],
    )
    def update_figure(invar, invar_2, invar_3, outvar, invar1_log, invar2_log, invar3_log, outvar_log, param_slider,
                      graph_type, color_use, color_dd, error_use, error_dd, filter_active, fit_use, fit_dd, fit_num, fit_conf, add_noise_var, fit_color,
                      fit_opacity, fit_sampling, id_type, param_center, param_log):
        for i in range(len(param_slider)):
            if param_log[i] == ['log']:
                param_slider[i] = [10**val for val in param_slider[i]]
                param_center[i] = 10**param_center[i]
        if invar is None:
            return go.Figure()
        sel_y = np.full((len(outdata),), True)
        dds_value = []
        for iteration, values in enumerate(param_slider):
            dds_value.append(invars[id_type[iteration]['index']])
            # filter for minimum
            sel_y_min = np.array(indata[dds_value[iteration]] >= param_slider[iteration][0])
            # filter for maximum
            sel_y_max = np.array(indata[dds_value[iteration]] <= param_slider[iteration][1])
            # print('iter ', iteration, 'filer', filter_active[iteration][0])
            if filter_active != [[]]:
                if filter_active[iteration] == ['act']:
                    sel_y = sel_y_min & sel_y_max & sel_y
        if graph_type == '1D':
            fig = go.Figure(
                data=[go.Scatter(
                    x=indata[invar][sel_y],
                    y=outdata[outvar][sel_y],
                    mode='markers',
                    name='data',
                    error_y=dict(type='data', array=outdata[error_dd][sel_y], visible= error_use == ['true']),
                    # text=[(invar, outvar) for i in range(len(indata[invar][sel_y]))],
                    # hovertemplate=" %{text} <br> %{x} <br> %{y}",
                )],
                layout=go.Layout(xaxis=dict(title=invar, rangeslider=dict(visible=True)), yaxis=dict(title=outvar))
            )
            if fit_use == ['show']:
                mesh_in, mesh_out, mesh_out_std, fit_dd_values = mesh_fit(param_slider, id_type, fit_dd, fit_num,
                                                                          param_center, [invar], [invar1_log],
                                                                          outvar, fit_sampling, add_noise_var)
                for i in range(len(fit_dd_values)):
                    fig.add_trace(go.Scatter(
                        x=mesh_in[i][invars.index(invar)],
                        y=mesh_out[i],
                        mode='lines',
                        name=f'fit: {fit_dd}={fit_dd_values[i]:.1e}',
                        line_color=colormap(indata[fit_dd].min(), indata[fit_dd].max(), fit_dd_values[i]),
                        marker_line=dict(coloraxis="coloraxis2"),
                    ))
                    fig.add_trace(go.Scatter(
                        x=np.hstack((mesh_in[i][invars.index(invar)], mesh_in[i][invars.index(invar)][::-1])),
                        y=np.hstack((mesh_out[i] + fit_conf * mesh_out_std[i], mesh_out[i][::-1] - fit_conf * mesh_out_std[i][::-1])),
                        showlegend=False,
                        fill='toself',
                        line_color=colormap(indata[fit_dd].min(), indata[fit_dd].max(), fit_dd_values[i]),
                        marker_line=dict(coloraxis="coloraxis2"),
                        opacity=fit_opacity,
                    ))
        elif graph_type == '2D':
            fig = go.Figure(
                data=[go.Scatter3d(
                    x=indata[invar][sel_y],
                    y=indata[invar_2][sel_y],
                    z=outdata[outvar][sel_y],
                    mode='markers',
                    name='Data',
                    error_z=dict(type='data', array=outdata[error_dd][sel_y], visible=error_use == ['true'], width= 10)
                )],
                layout=go.Layout(scene=dict(xaxis_title=invar, yaxis_title=invar_2, zaxis_title=outvar))
            )
            if fit_use == ['show'] and invar != invar_2:
                mesh_in, mesh_out, mesh_out_std, fit_dd_values = mesh_fit(param_slider, id_type, fit_dd, fit_num,
                                                                          param_center, [invar, invar_2],
                                                                          [invar1_log, invar2_log], outvar,
                                                                          fit_sampling, add_noise_var)
                for i in range(len(fit_dd_values)):
                    fig.add_trace(go.Surface(
                        x=mesh_in[i][invars.index(invar)].reshape((fit_sampling, fit_sampling)),
                        y=mesh_in[i][invars.index(invar_2)].reshape((fit_sampling, fit_sampling)),
                        z=mesh_out[i].reshape((fit_sampling, fit_sampling)),
                        name=f'fit: {fit_dd}={fit_dd_values[i]:.2f}',
                        surfacecolor=fit_dd_values[i] * np.ones([fit_sampling, fit_sampling])
                            if fit_color == 'multi-fit' else
                            (mesh_in[i][invars.index(color_dd)].reshape((fit_sampling, fit_sampling))
                             if (fit_color == 'marker-color' and color_dd in invars) else
                             mesh_out[i].reshape((fit_sampling, fit_sampling))),
                        opacity=fit_opacity,
                        coloraxis="coloraxis2" if (fit_color == 'multi-fit' or
                            (fit_color == 'output' and (color_dd != outvar and color_dd != 'OUTPUT'))) else "coloraxis",
                        showlegend=True if len(invars) > 2 else False,
                    ))
                    if fit_conf > 0:
                        fig.add_trace(go.Surface(
                            x=mesh_in[i][invars.index(invar)].reshape((fit_sampling, fit_sampling)),
                            y=mesh_in[i][invars.index(invar_2)].reshape((fit_sampling, fit_sampling)),
                            z=mesh_out[i].reshape((fit_sampling, fit_sampling)) + fit_conf * mesh_out_std[i].reshape((fit_sampling, fit_sampling)),
                            showlegend=False,
                            name=f'fit+v: {fit_dd}={fit_dd_values[i]:.2f}',
                            surfacecolor=fit_dd_values[i] * np.ones([fit_sampling, fit_sampling])
                                if fit_color == 'multi-fit' else
                                (mesh_in[i][invars.index(color_dd)].reshape((fit_sampling, fit_sampling))
                                 if (fit_color == 'marker-color' and color_dd in invars) else
                                 mesh_out[i].reshape((fit_sampling, fit_sampling))),
                            opacity=fit_opacity,
                            coloraxis="coloraxis2" if (fit_color == 'multi-fit' or
                                (fit_color == 'output' and (color_dd != outvar and color_dd != 'OUTPUT')))
                                else "coloraxis",
                        ))
                        fig.add_trace(go.Surface(
                            x=mesh_in[i][invars.index(invar)].reshape((fit_sampling, fit_sampling)),
                            y=mesh_in[i][invars.index(invar_2)].reshape((fit_sampling, fit_sampling)),
                            z=mesh_out[i].reshape((fit_sampling, fit_sampling)) - fit_conf * mesh_out_std[i].reshape((fit_sampling, fit_sampling)),
                            showlegend=False,
                            name=f'fit-v: {fit_dd}={fit_dd_values[i]:.2f}',
                            surfacecolor=fit_dd_values[i] * np.ones([fit_sampling, fit_sampling])
                                if fit_color == 'multi-fit' else
                                (mesh_in[i][invars.index(color_dd)].reshape((fit_sampling, fit_sampling))
                                 if (fit_color == 'marker-color' and color_dd in invars) else
                                 mesh_out[i].reshape((fit_sampling, fit_sampling))),
                            opacity=fit_opacity,
                            coloraxis="coloraxis2" if (fit_color == 'multi-fit' or
                                (fit_color == 'output' and (color_dd != outvar and color_dd != 'OUTPUT')))
                                else "coloraxis",
                        ))
                fig.update_layout(coloraxis2=dict(
                    colorbar=dict(title=outvar if fit_color == 'output' else fit_dd),
                    cmin=min(fit_dd_values) if fit_color == 'multi-fit' else None,
                    cmax=max(fit_dd_values) if fit_color == 'multi-fit' else None,
                ))
        elif graph_type == '2D contour':
            mesh_in, mesh_out, mesh_out_std, fit_dd_values = mesh_fit(param_slider, id_type, fit_dd, fit_num,
                                                                      param_center, [invar, invar_2],
                                                                      [invar1_log, invar2_log], outvar,
                                                                      fit_sampling, add_noise_var)
            data_x = mesh_in[0][invars.index(invar)]
            data_y = mesh_in[0][invars.index(invar_2)]
            fig = go.Figure()
            if min(data_x) != max(data_x):
                if min(data_y) != max(data_y):
                    fig.add_trace(go.Scatter(
                        x=indata[invar][sel_y],
                        y=indata[invar_2][sel_y],
                        mode='markers',
                        name='Data',
                    ))
                    fig.add_trace(go.Contour(
                        x=mesh_in[0][invars.index(invar)],
                        y=mesh_in[0][invars.index(invar_2)],
                        z=mesh_out[0],
                        contours_coloring='heatmap',
                        contours_showlabels=True,
                        coloraxis='coloraxis2',
                        name='fit',
                    ))
                    fig.update_xaxes(
                        range=[log10(min(fig.data[1]['x'])), log10(max(fig.data[1]['x']))] if invar1_log == ['log']
                        else [min(fig.data[1]['x']), max(fig.data[1]['x'])])
                    fig.update_yaxes(
                        range=[log10(min(fig.data[1]['y'])), log10(max(fig.data[1]['y']))] if invar2_log == ['log']
                        else [min(fig.data[1]['y']), max(fig.data[1]['y'])])
                    fig.update_layout(xaxis_title=invar,
                                      yaxis_title=invar_2,
                                      coloraxis2=dict(colorbar=dict(title=outvar),
                                                      colorscale='solar',
                                                      cmin=min(fig.data[1]['z']),
                                                      cmax=max(fig.data[1]['z'])))
                else:
                    fig.update_layout(title="y-data is constant, no contour-plot possible")
            else:
                fig.update_layout(title="x-data is constant, no contour-plot possible")
        elif graph_type == '3D':
            fig = go.Figure(
                data=go.Scatter3d(
                    x=indata[invar][sel_y],
                    y=indata[invar_2][sel_y],
                    z=indata[invar_3][sel_y],
                    mode='markers',
                    marker=dict(
                            color=outdata[outvar][sel_y],
                            coloraxis="coloraxis2",
                        ),
                    name='Data',
                ),
                layout=go.Layout(scene=dict(xaxis_title=invar, yaxis_title=invar_2, zaxis_title=invar_3)),
            )
            fig.update_layout(coloraxis2=dict(
                colorbar=dict(title=outvar),
            ))
            if fit_use == ['show'] and len({invar, invar_2, invar_3}) == 3:
                mesh_in, mesh_out, mesh_out_std, fit_dd_values = mesh_fit(param_slider, id_type, fit_dd, fit_num,
                                                                          param_center, [invar, invar_2, invar_3],
                                                                          [invar1_log, invar2_log, invar3_log], outvar,
                                                                          fit_sampling, add_noise_var)
                for i in range(len(fit_dd_values)):
                    fig.add_trace(
                        go.Isosurface(
                            x=mesh_in[i][invars.index(invar)],
                            y=mesh_in[i][invars.index(invar_2)],
                            z=mesh_in[i][invars.index(invar_3)],
                            value=mesh_out[i],
                            surface_count=fit_num,
                            coloraxis="coloraxis2",
                            isomin=mesh_out[i].min() * 1.1,
                            isomax=mesh_out[i].max() * 0.9,
                            caps=dict(x_show=False, y_show=False, z_show=False),
                            opacity=fit_opacity,
                        ),
                    )
        else:
            fig = go.Figure()
        fig.update_layout(legend=dict(xanchor="left", x=0.01))
        # log scale
        log_dict = {'1D': (invar1_log, outvar_log),
                    '2D': (invar1_log, invar2_log, outvar_log),
                    '2D contour': (invar1_log, invar2_log),
                    '3D': (invar1_log, invar2_log, invar3_log),}
        log_list = ['linear' if log is None or len(log) == 0 else log[0] for log in log_dict[graph_type]]
        log_key = ['xaxis', 'yaxis', 'zaxis']
        comb_dict = dict(zip(log_key, [{'type': log} for log in log_list]))
        if len(log_list) < 3 :
            fig.update_layout(**comb_dict)
        else:
            fig.update_scenes(**comb_dict)
        # color
        if color_use == ['true']: # TODO: trigger-detection no new fig just update
            if fit_use == ['show'] and (graph_type=='2D' and (fit_color=='multi-fit' and color_dd==fit_dd)):
                fig.update_traces(
                    marker=dict(
                        coloraxis="coloraxis2",
                        color=indata[color_dd][sel_y] if color_dd in indata.dtype.names else outdata[color_dd][sel_y],
                    ),
                    selector=dict(mode='markers'),
                )
            elif graph_type == '3D':
                fig.update_traces(
                    marker=dict(
                        coloraxis="coloraxis2",
                        color=outdata[outvar][sel_y],
                    ),
                    selector=dict(mode='markers'),
                )
            elif graph_type=='1D':
                fig.update_traces(
                    marker=dict(
                        coloraxis="coloraxis2",
                        color=outdata[outvar][sel_y] if color_dd == 'OUTPUT' else
                        (indata[color_dd][sel_y] if color_dd in indata.dtype.names else outdata[color_dd][sel_y]),
                    ),
                    selector=dict(mode='markers'),
                )
                if color_dd==fit_dd:
                    fig.update_layout(coloraxis2=dict(colorscale='cividis', colorbar=dict(title=fit_dd)))
                elif color_dd == 'OUTPUT':
                    fig.update_layout(coloraxis2=dict(colorscale='plasma', colorbar=dict(title=outvar)))
                else:
                    fig.update_layout(coloraxis2=dict(colorscale='plasma', colorbar=dict(title=color_dd)))
            elif graph_type =='2D contour':
                fig.update_traces(
                    marker=dict(
                        coloraxis="coloraxis",
                        color=outdata[outvar][sel_y] if color_dd == 'OUTPUT' else
                        (indata[color_dd][sel_y] if color_dd in indata.dtype.names else outdata[color_dd][sel_y]),
                    ),
                    selector=dict(mode='markers'),
                )
                if color_dd == outvar or color_dd == 'OUTPUT':
                    fig.update_traces(marker_coloraxis="coloraxis2", selector=dict(mode='markers'))
                else:
                    fig.update_layout(coloraxis=dict(colorbar=dict(title=color_dd, x=1.1),
                                                     colorscale='ice'))
            else:
                fig.update_traces(
                    marker=dict(
                        coloraxis="coloraxis",
                        color=outdata[outvar][sel_y] if color_dd == 'OUTPUT' else
                        (indata[color_dd][sel_y] if color_dd in indata.dtype.names else outdata[color_dd][sel_y]),
                    ),
                    selector=dict(mode='markers'),
                )
                fig.update_layout(coloraxis=dict(
                    colorbar=dict(title=outvar if color_dd == 'OUTPUT' else color_dd, x=1.1),
                    colorscale='viridis',
                ))
        fig.update_layout(height=graph_height)
        return fig


    def mesh_fit(param_slider, id_type, fit_dd, fit_num, param_center, invar_list, invar_log_list, outvar, num_samples, add_noise_var):
        try:  # collecting min/max of slider for variable of multifit
            fit_dd_min, fit_dd_max = param_slider[[i['index'] for i in id_type].index(invars.index(fit_dd))]
        except ValueError:
            fit_dd_min = min(indata[fit_dd])
            fit_dd_max = max(indata[fit_dd])

        if fit_num == 1: # generate list of value of variable of multifit
            fit_dd_values = np.array([(fit_dd_max + fit_dd_min) / 2])
        else:
            fit_dd_values = np.linspace(fit_dd_min, fit_dd_max, fit_num)

        for iteration, fit_dd_value in enumerate(fit_dd_values): # iteration for each fit
            # set fit parameter for all invars as center of range
            fit_params = [(max(indata[var_invar]) + min(indata[var_invar])) / 2 for var_invar in invars]
            # for all invars with filter change fit_param to center defined by filter
            flt_ind_list = [] # list of filter indices
            for i, center_values in enumerate(param_center):
                flt_ind_list.append(id_type[i]['index'])
                fit_params[flt_ind_list[i]] = center_values
            # change param of fit-variable
            fit_params[invars.index(fit_dd)] = fit_dd_value
            # change param for axis invars
            for i, ax_in in enumerate(invar_list):
                if invars.index(ax_in) in flt_ind_list:
                    ax_min, ax_max = param_slider[flt_ind_list.index(invars.index(ax_in))]
                else:
                    ax_min = min(indata[ax_in])
                    ax_max = max(indata[ax_in])
                if invar_log_list[i] == ['log']:
                    fit_params[invars.index(ax_in)] = np.logspace(log10(ax_min), log10(ax_max), num_samples)
                else:
                    fit_params[invars.index(ax_in)] = np.linspace(ax_min, ax_max, num_samples)
            grid = np.meshgrid(*fit_params) # generate grid
            x_pred = np.vstack([g.flatten() for g in grid]).T  # extract vector for predict
            fit_data, fit_var = sur.predict(x_pred, add_noise_var == ['add']) # generate fit data an variance
            # generated data
            new_mesh_in = np.array([[grid[invars.index(invar)].flatten() for invar in invars]])
            new_mesh_out = np.array([fit_data[:, outvars.index(outvar)]])
            new_mesh_out_std = np.array([np.sqrt(fit_var[:, 0])])
            # stack data together
            if iteration == 0:
                mesh_in = new_mesh_in
                mesh_out = new_mesh_out
                mesh_out_std = new_mesh_out_std
            else:
                mesh_in = np.vstack((mesh_in, new_mesh_in))
                mesh_out = np.vstack((mesh_out, new_mesh_out))
                mesh_out_std = np.vstack((mesh_out_std, new_mesh_out_std))
        return mesh_in, mesh_out, mesh_out_std, fit_dd_values


    def create_slider(dd_value):
        ind = invars.index(dd_value)
        slider_min = indata[dd_value].min()
        slider_max = indata[dd_value].max()
        step_exponent = -3
        new_slider = dcc.RangeSlider(
            id={'type': 'param-slider', 'index': ind},
            step=10 ** step_exponent,
            min=slider_min,
            max=slider_max,
            value=[slider_min, slider_max],
            marks={slider_min: str(round(slider_min, -step_exponent)),
                   slider_max: str(round(slider_max, -step_exponent))},
        )
        return new_slider


    def colormap(cmin, cmax, c):
        if cmin == cmax:
            c_scal = 0.5
        else:
            c_scal = (c-cmin)/(cmax-cmin)
        return color2hex(colormaps.cividis(c_scal))

    return app
