From d6c553796a77f62f8f342ea12fe6920ae09c21e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Fredrik=20Ki=C3=A6r?= <31612826+anders-kiaer@users.noreply.github.com> Date: Fri, 5 Apr 2019 11:48:44 +0200 Subject: [PATCH] Add travis and pypi deployment (#3) --- .gitignore | 1 + .travis.yml | 35 ++++++ README.md | 17 +-- pytest.ini | 3 + requirements.txt | 1 - setup.py | 28 ++++- tests/data/parameters.csv | 101 ++++++++++++++++++ tests/test_parameter_distribution.py | 40 +++++++ webviz_subsurface/__init__.py | 7 ++ webviz_subsurface/containers/__init__.py | 5 +- .../containers/_history_match.py | 2 +- .../containers/_parameter_distribution.py | 88 +++++++-------- .../containers/_summary_stats.py | 81 +++++++------- 13 files changed, 315 insertions(+), 94 deletions(-) create mode 100644 .travis.yml create mode 100644 pytest.ini delete mode 100644 requirements.txt create mode 100644 tests/data/parameters.csv create mode 100644 tests/test_parameter_distribution.py diff --git a/.gitignore b/.gitignore index 61eba3185..741673583 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ venv .pytest_cache *.pyc .DS_Store +dist diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..687c4aaa9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +language: python +cache: pip + +addons: + chrome: stable + +python: + - "3.6" + +matrix: + fast_finish: true + +before_install: + - sudo apt-get -qq update + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + +install: + - wget https://chromedriver.storage.googleapis.com/73.0.3683.68/chromedriver_linux64.zip + - unzip chromedriver_linux64.zip + - export PATH=$PATH:$PWD + +script: + - pip install . + - pip install .[tests] + - pycodestyle webviz_subsurface/containers webviz_subsurface/datainput tests + - pytest tests + +deploy: + - provider: pypi + user: webviz + password: + secure: raZvnkESL4A7R2s/IW0amym1p4rOvg3WeJ1hpnS13+97OPWnj3QgusHFmxlbU8JV1LzeotDrLJKDFtNxUSJlqi52SuM1MnaGA4zrngF4yHCgfCNHIRNMYRh53jcEra8vE0QVMv3k+0uQHV2bHnvCkFDTa3ktJo6ovCIrBH+RBRSukz2KlzNiUToNZvvUX5fI9jBCk7vDarWOc9YJqew5gsKzeOPp7pdvis5Em3T0psB6Y3Gg56w8jZ/NrpEwtJ+otxBlgk+Qrwg4c/7PqJ8Ib8JHXmnLMKHv/SXdBpJObq+2lI8bdgxM8RDKXGwi+5/p29B5tmzaS37fmJZ25Js7ZfnND3hjvEnnJMiDKjBT4qEqsIA7wSZfbBVczRjd3uf8BoqVAE/O9/kG9LJn+zcvLy3+Neb4Gi7e1bgJ20DgPupRU6D6UR7TiFtkiksF97pjfcOYRQ5iM/y+y6Uhj1BT2mONg4XMgUa6PO/JF3OanOwbcz5EfEa3fAvQu90CV71o150hvIrarQiGhKoD7B533Ehh+gSWgvN8TF6rR26UvqIntqyaVGAbAzcGGFQmF1pE0ZRtrSlk9xShH4jxD5EPRnx7G9ip+sU/cfneNFRKG/HAPIh6AOiWqnKlZx0oDaF7eWB78/wGAk4K5YFb1C7nM6fyLzCDDxrJrgvtNXovTgw= + on: + tags: true diff --git a/README.md b/README.md index 594b6f19a..486a5c67e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Webviz Subsurface configuration +# Webviz subsurface configuration ### Introduction @@ -10,17 +10,19 @@ plugins in [webviz-config](https://github.com/equinor/webviz-config). ### Installation As Dash is using Python3-only functionality, you should create a Python3 -virtual environment before installation. One way of doing this in Equinor is +virtual environment before installation. One way of doing this is ```bash -export PYTHON_VERSION=3.7.1 -source /prog/sdpsoft/env.sh - PATH_TO_VENV='./my_new_venv' python3 -m virtualenv $PATH_TO_VENV source $PATH_TO_VENV/bin/activate ``` -In order to install the utility, run +The easiest way of installing this package is to run +```bash +pip install webviz-subsurface +``` + +If you want to install the latest code you can instead run ```bash git clone git@github.com:Equinor/webviz-subsurface.git cd webviz-subsurface @@ -32,7 +34,8 @@ pip install . For general usage, see the documentation on [webviz-config](https://github.com/equinor/webviz-config). -Take a look at this configuration example for something subsurface specific. +Take a look at [this configuration example](./examples/basic_example.yaml) +for something subsurface specific. ### Creating new elements diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..3eca56b7f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tests/ +webdriver = Chrome diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8febe5ffe..000000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -scipy>=1.2.1 diff --git a/setup.py b/setup.py index 08ea85525..905e9d01f 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,25 @@ from setuptools import setup, find_packages +with open('README.md', 'r') as fh: + long_description = fh.read() + +tests_require = [ + 'chromedriver-binary', + 'dash>=0.38.0', + 'ipdb', + 'percy', + 'selenium', + 'flake8', + 'pylint', + 'pytest-dash>=2.1.1', + 'mock' +] + setup( name='webviz-subsurface', - version='0.1', description='Webviz config containers for subsurface data', + long_description=long_description, + long_description_content_type='text/markdown', url='https://github.com/equinor/webviz-subsurface', author='R&T Equinor', packages=find_packages(exclude=['tests']), @@ -16,5 +32,15 @@ 'HistoryMatch = webviz_subsurface.containers:HistoryMatch' ] }, + install_requires=[ + 'scipy>=1.2.1', + 'webviz-plotly>=0.0.1', + 'webviz-subsurface-components>=0.0.2', + 'webviz-config>=0.0.2' + ], + tests_require=tests_require, + extras_require={'tests': tests_require}, + setup_requires=['setuptools_scm>=3.2.0'], + use_scm_version=True, zip_safe=False ) diff --git a/tests/data/parameters.csv b/tests/data/parameters.csv new file mode 100644 index 000000000..185e0dfeb --- /dev/null +++ b/tests/data/parameters.csv @@ -0,0 +1,101 @@ +REAL,param1,param2,param3,param4,param5 +0,-0.875856,-0.407236,-1.94365,-1.73292,-0.526659 +1,-0.470495,0.291171,-0.396227,-4.56093,-0.20937 +2,0.110624,0.444149,-1.00413,-3.7833,-0.690756 +3,0.912676,0.0384368,-1.67812,-1.13615,-0.236384 +4,-0.7349,0.259419,-0.358038,-1.33444,-0.104108 +5,0.750027,0.141387,-1.15614,-0.942754,-1.66625 +6,-0.618177,0.725788,-1.31892,-3.48645,-1.51785 +7,-0.383238,-0.102866,-1.83967,-3.66075,-1.22701 +8,-0.763173,-0.39908,-0.529304,-3.8636,-1.02242 +9,0.436434,-0.528909,-0.897819,-3.50828,-1.96837 +10,-0.947978,-0.47117,-0.712116,-3.85766,-1.82098 +11,-0.0587859,0.271093,-0.49748,-1.0697,-0.28255 +12,0.962515,0.708247,-1.58361,-4.46348,-1.65848 +13,0.912299,0.536269,-1.64083,-2.71689,-0.751055 +14,0.38769,-0.715727,-1.16909,-2.95051,-1.86912 +15,-0.57312,-0.323231,-1.27579,-1.17063,-0.678474 +16,-0.244411,0.577235,-0.798796,-4.27459,-1.61182 +17,0.586486,0.756078,-1.16091,-0.183956,-1.6255 +18,0.313436,-0.377103,-1.55707,-1.79905,-1.17159 +19,-0.337405,-0.1697,-1.81087,-1.48696,-0.856727 +20,0.63199,0.278797,-0.441383,-0.772516,-1.08143 +21,-0.93634,0.28887,-1.52087,-2.03013,-0.661198 +22,-0.621961,0.503005,-1.44524,-0.391462,-1.15773 +23,-0.481156,-0.777296,-0.121033,-4.60376,-1.36652 +24,0.255161,-0.661237,-1.75495,-2.78065,-1.14094 +25,-0.915796,0.164361,-0.482236,-3.7641,-0.0298055 +26,0.788199,-0.0770462,-1.76516,-3.96855,-0.894714 +27,-0.184485,0.0607133,-0.811169,-3.85004,-0.487889 +28,0.135897,0.249731,-0.318907,-0.93984,-0.588588 +29,0.414382,0.111156,-0.921854,-1.38164,-1.77654 +30,-0.532518,0.526784,-0.541737,-4.98728,-1.88406 +31,0.923824,-0.552733,-0.135931,-0.0422384,-0.90053 +32,-0.0801342,-0.0738262,-0.0851388,-0.44625,-1.25627 +33,0.309105,-0.899647,-0.553205,-0.941461,-1.93888 +34,0.883303,-0.577276,-0.26315,-2.22259,-1.04459 +35,-0.788199,-0.564361,-1.18696,-2.37771,-1.92592 +36,-0.798785,-0.729763,-0.541841,-4.03811,-1.78112 +37,-0.240605,-0.911822,-0.464576,-2.94471,-0.269181 +38,0.997391,-0.509785,-1.15155,-0.00164788,-0.632892 +39,0.443709,-0.525793,-0.49534,-1.32166,-0.902483 +40,0.543892,-0.452326,-1.08874,-0.44876,-1.75581 +41,0.90777,0.813169,-1.40148,-3.45953,-0.550609 +42,-0.0494279,0.687413,-0.979871,-3.15002,-1.09412 +43,-0.418402,-0.84238,-0.764285,-1.91601,-0.931801 +44,-0.512313,-0.0380025,-0.404476,-4.9013,-1.06259 +45,0.283486,0.414684,-1.42369,-1.6782,-1.78168 +46,0.29461,-0.604647,-0.923689,-4.73356,-1.36709 +47,-0.0663039,0.463323,-1.65703,-3.25854,-1.77914 +48,-0.391704,-0.607917,-1.12668,-3.66465,-0.0150379 +49,0.990061,0.762999,-0.182279,-1.65236,-0.181911 +50,-0.581204,-0.348323,-0.599561,-2.14749,-0.623294 +51,0.300247,0.468538,-0.677525,-0.960034,-1.06637 +52,0.167333,-0.63414,-0.101815,-1.02794,-1.92073 +53,-0.819369,0.810563,-0.927937,-3.79484,-0.828513 +54,0.788968,-0.756235,-1.72724,-1.37424,-1.35925 +55,-0.861144,-0.596493,-0.530796,-1.07202,-0.403359 +56,-0.394824,0.127789,-1.52484,-3.47559,-0.644352 +57,0.370526,-0.622035,-1.26614,-4.82398,-0.954674 +58,0.214625,0.622978,-0.480739,-4.41922,-0.97834 +59,0.945461,-0.374867,-0.263035,-2.67721,-1.77022 +60,-0.993986,0.331516,-0.982376,-1.33075,-1.75263 +61,-0.911882,0.0382966,-0.156135,-2.13532,-0.94761 +62,0.231348,-0.474135,-1.15367,-4.54257,-1.82315 +63,-0.942791,-0.0680879,-0.392099,-3.10322,-1.66363 +64,0.041372,0.369994,-1.38335,-3.46936,-1.08482 +65,-0.763821,-0.0674544,-1.42241,-1.10404,-1.72444 +66,-0.176977,0.598034,-0.198226,-2.09723,-0.0317826 +67,0.212643,-0.211018,-0.7801,-3.93891,-0.076047 +68,-0.404951,-0.0849538,-0.210486,-2.6621,-0.185013 +69,0.233193,0.711441,-0.137755,-0.720327,-1.63427 +70,0.441375,0.488368,-1.60014,-0.81826,-1.78532 +71,0.307021,-0.113093,-1.79504,-4.32028,-0.528862 +72,0.622704,-0.118087,-0.71593,-0.825902,-0.0794506 +73,-0.811703,-0.184094,-0.619623,-4.38098,-1.99725 +74,0.486881,-0.687405,-0.485178,-2.80762,-0.482481 +75,-0.599717,-0.206133,-0.712509,-3.14518,-1.22203 +76,-0.685642,0.0573893,-1.85431,-4.70566,-0.47863 +77,-0.724203,-0.932711,-1.98806,-4.94827,-0.299622 +78,-0.613565,-0.344292,-0.464429,-0.163956,-1.62096 +79,-0.261722,-0.107082,-1.21124,-1.04311,-0.937077 +80,0.248658,-0.891504,-0.895991,-0.896498,-0.177235 +81,-0.847755,-0.328462,-1.54977,-4.93907,-0.468314 +82,0.784201,0.598249,-0.946083,-1.11915,-1.2591 +83,-0.605287,0.878137,-1.89427,-0.635992,-1.22778 +84,-0.773523,-0.210882,-0.84435,-3.56528,-1.36284 +85,0.541718,0.381457,-1.68663,-1.21574,-1.38599 +86,0.881217,0.407449,-0.954723,-1.3254,-1.35584 +87,0.5227,0.990008,-1.95251,-0.917875,-0.464534 +88,-0.547445,-0.132311,-1.61173,-0.0952772,-1.60458 +89,-0.444519,-0.0146918,-0.977614,-2.88854,-1.95356 +90,-0.13972,-0.454793,-0.808139,-1.6017,-0.1209 +91,-0.114501,0.170237,-1.55051,-3.81532,-1.11993 +92,-0.917638,-0.978958,-0.792755,-0.97681,-1.31486 +93,0.676335,-0.177007,-0.40066,-4.99286,-0.262801 +94,-0.574525,-0.0769377,-0.567106,-1.40762,-0.219847 +95,0.729473,0.193766,-1.41404,-1.44003,-1.22203 +96,-0.639979,0.815374,-1.86193,-1.80513,-1.10395 +97,0.00874448,-0.747198,-1.3857,-0.673076,-1.65204 +98,-0.13628,0.927141,-0.984301,-4.16289,-1.05452 +99,0.746946,0.748492,-0.111114,-0.483965,-0.204353 diff --git a/tests/test_parameter_distribution.py b/tests/test_parameter_distribution.py new file mode 100644 index 000000000..df6e161fb --- /dev/null +++ b/tests/test_parameter_distribution.py @@ -0,0 +1,40 @@ +import mock +import dash +import pandas as pd +from pytest_dash.wait_for import wait_for_element_by_css_selector +from webviz_config.common_cache import cache +from webviz_subsurface.containers import _parameter_distribution + + +# mocked functions +get_parameters = 'webviz_subsurface.containers'\ + '._parameter_distribution.get_parameters' + + +def test_parameter_dist(dash_threaded): + + app = dash.Dash(__name__) + app.css.config.serve_locally = True + app.scripts.config.serve_locally = True + app.config.suppress_callback_exceptions = True + cache.init_app(app.server) + driver = dash_threaded.driver + container_settings = {'scratch_ensembles': {'iter-0': ''}} + ensemble = 'iter-0' + + with mock.patch(get_parameters) as mock_parameters: + mock_parameters.return_value = pd.read_csv('tests/data/parameters.csv') + + p = _parameter_distribution.\ + ParameterDistribution(app, container_settings, ensemble) + + app.layout = p.layout + dash_threaded(app) + + my_component = wait_for_element_by_css_selector( + driver, + f'#{p.dropdown_vector_id}' + ) + + if 'REAL' != my_component.text: + raise AssertionError() diff --git a/webviz_subsurface/__init__.py b/webviz_subsurface/__init__.py index e69de29bb..a037cbd2c 100644 --- a/webviz_subsurface/__init__.py +++ b/webviz_subsurface/__init__.py @@ -0,0 +1,7 @@ +from pkg_resources import get_distribution, DistributionNotFound + +try: + __version__ = get_distribution(__name__).version +except DistributionNotFound: + # package is not installed + pass diff --git a/webviz_subsurface/containers/__init__.py b/webviz_subsurface/containers/__init__.py index 2121ec7b2..97676b740 100644 --- a/webviz_subsurface/containers/__init__.py +++ b/webviz_subsurface/containers/__init__.py @@ -1,7 +1,8 @@ '''### _Subsurface specific containers_ -These are containers relevant within subsurface workflows. Most of them -rely on the setting `scratch_ensemble` configuration within the `container_settings`. +These are containers relevant within subsurface workflows. Most of them +rely on the setting `scratch_ensemble` configuration within the +`container_settings`. I.e. you could have ```yaml title: Reek Webviz Demonstration diff --git a/webviz_subsurface/containers/_history_match.py b/webviz_subsurface/containers/_history_match.py index 39c99e810..b2a6b896b 100644 --- a/webviz_subsurface/containers/_history_match.py +++ b/webviz_subsurface/containers/_history_match.py @@ -24,7 +24,7 @@ class HistoryMatch: ''' def __init__(self, container_settings, ensembles, observation_file: Path, - title: str='History Match'): + title: str = 'History Match'): self.observation_file = observation_file self.title = title diff --git a/webviz_subsurface/containers/_parameter_distribution.py b/webviz_subsurface/containers/_parameter_distribution.py index c2a51dc85..95e136583 100644 --- a/webviz_subsurface/containers/_parameter_distribution.py +++ b/webviz_subsurface/containers/_parameter_distribution.py @@ -32,36 +32,36 @@ def __init__(self, app, container_settings, ensemble, # Finding all parameters: self.ensemble_path = container_settings['scratch_ensembles'][ensemble] self.parameter_columns = sorted(list( - get_parameters(self.ensemble_path).columns)) + get_parameters(self.ensemble_path).columns)) self.set_callbacks(app) @property def layout(self): return html.Div([ - html.H2(self.title), - html.P('Plot type:', style={'font-weight': 'bold'}), - dcc.RadioItems(id=self.radio_plot_type_id, - options=[{'label': i, 'value': i} for i in - ['Histogram', 'Pairwise correlation']], - value='Histogram'), - html.Div(id=self.histogram_div_id, - children=[ - html.P('Parameter:', - style={'font-weight': 'bold'}), - dcc.Dropdown(id=self.dropdown_vector_id, - clearable=False, - options=[{'label': i, 'value': i} for - i in self.parameter_columns], - value=self.parameter_columns[0]), - ]), - dcc.Graph(id=self.chart_id, - config={ - 'displaylogo': False, - 'modeBarButtonsToRemove': ['sendDataToCloud'] - } - ) - ]) + html.H2(self.title), + html.P('Plot type:', style={'font-weight': 'bold'}), + dcc.RadioItems(id=self.radio_plot_type_id, + options=[{'label': i, 'value': i} for i in + ['Histogram', 'Pairwise correlation']], + value='Histogram'), + html.Div(id=self.histogram_div_id, + children=[ + html.P('Parameter:', + style={'font-weight': 'bold'}), + dcc.Dropdown(id=self.dropdown_vector_id, + clearable=False, + options=[{'label': i, 'value': i} for + i in self.parameter_columns], + value=self.parameter_columns[0]), + ]), + dcc.Graph(id=self.chart_id, + config={ + 'displaylogo': False, + 'modeBarButtonsToRemove': ['sendDataToCloud'] + } + ) + ]) def set_callbacks(self, app): @app.callback(Output(self.chart_id, 'figure'), @@ -96,17 +96,17 @@ def get_parameters(ensemble_path) -> pd.DataFrame: @cache.memoize(timeout=cache.TIMEOUT) def render_histogram(ensemble_path, parameter): data = { - 'x': get_parameters(ensemble_path)[parameter], - 'type': 'histogram' - } + 'x': get_parameters(ensemble_path)[parameter], + 'type': 'histogram' + } layout = { - 'bargap': 0.05, - 'font': {'family': 'Equinor'}, - 'xaxis': {'family': 'Equinor'}, - 'yaxis': {'family': 'Equinor'}, - 'hoverlabel': {'font': {'family': 'Equinor'}} - } + 'bargap': 0.05, + 'font': {'family': 'Equinor'}, + 'xaxis': {'family': 'Equinor'}, + 'yaxis': {'family': 'Equinor'}, + 'hoverlabel': {'font': {'family': 'Equinor'}} + } return {'data': [data], 'layout': layout} @@ -118,18 +118,18 @@ def render_matrix(ensemble_path): values = list(data.corr().values) data = { - 'type': 'heatmap', - 'x': data.columns, - 'y': data.columns, - 'z': values - } + 'type': 'heatmap', + 'x': data.columns, + 'y': data.columns, + 'z': values + } layout = { - 'margin': {'l': 200}, - 'font': {'family': 'Equinor'}, - 'xaxis': {'family': 'Equinor'}, - 'yaxis': {'family': 'Equinor'}, - 'hoverlabel': {'font': {'family': 'Equinor'}} - } + 'margin': {'l': 200}, + 'font': {'family': 'Equinor'}, + 'xaxis': {'family': 'Equinor'}, + 'yaxis': {'family': 'Equinor'}, + 'hoverlabel': {'font': {'family': 'Equinor'}} + } return {'data': [data], 'layout': layout} diff --git a/webviz_subsurface/containers/_summary_stats.py b/webviz_subsurface/containers/_summary_stats.py index 53cc69131..489107443 100644 --- a/webviz_subsurface/containers/_summary_stats.py +++ b/webviz_subsurface/containers/_summary_stats.py @@ -20,8 +20,13 @@ class SummaryStats: * `title`: Optional title for the container. ''' - def __init__(self, app, container_settings, ensemble, - sampling: str='monthly', title: str='Simulation time series'): + def __init__( + self, + app, + container_settings, + ensemble, + sampling: str = 'monthly', + title: str = 'Simulation time series'): self.title = title self.dropwdown_vector_id = 'dropdown-vector-{}'.format(uuid4()) @@ -42,25 +47,25 @@ def __init__(self, app, container_settings, ensemble, @property def layout(self): return html.Div([ - html.H2(self.title), - html.P('Summary Vector:', style={'font-weight': 'bold'}), - dcc.Dropdown(id=self.dropwdown_vector_id, - clearable=False, + html.H2(self.title), + html.P('Summary Vector:', style={'font-weight': 'bold'}), + dcc.Dropdown(id=self.dropwdown_vector_id, + clearable=False, + options=[{'label': i, 'value': i} + for i in self.smry_columns], + value=self.smry_columns[0]), + html.P('Plot type:', style={'font-weight': 'bold'}), + dcc.RadioItems(id=self.radio_plot_type_id, options=[{'label': i, 'value': i} - for i in self.smry_columns], - value=self.smry_columns[0]), - html.P('Plot type:', style={'font-weight': 'bold'}), - dcc.RadioItems(id=self.radio_plot_type_id, - options=[{'label': i, 'value': i} - for i in ['Realizations', 'Statistics']], - value='Realizations'), - dcc.Graph(id=self.chart_id, - config={ - 'displaylogo': False, - 'modeBarButtonsToRemove': ['sendDataToCloud'] - } - ) - ]) + for i in ['Realizations', 'Statistics']], + value='Realizations'), + dcc.Graph(id=self.chart_id, + config={ + 'displaylogo': False, + 'modeBarButtonsToRemove': ['sendDataToCloud'] + } + ) + ]) def set_callbacks(self, app): @app.callback(Output(self.chart_id, 'figure'), @@ -69,12 +74,12 @@ def set_callbacks(self, app): def update_plot(vector, summary_plot_type): if summary_plot_type == 'Realizations': return render_realization_plot( - self.ensemble_path, - self.sampling, vector) + self.ensemble_path, + self.sampling, vector) if summary_plot_type == 'Statistics': return render_stat_plot( - self.ensemble_path, - self.sampling, vector) + self.ensemble_path, + self.sampling, vector) def add_webvizstore(self): return [(get_summary_data, [{'ensemble_path': self.ensemble_path, @@ -104,22 +109,22 @@ def render_realization_plot(ensemble_path, sampling, vector): sampling)[['REAL', 'DATE', vector]] traces = [{ - 'x': df['DATE'], - 'customdata': df['REAL'], - 'y': df[vector], - 'name': name, - 'type': 'line' - } for name, df in data.groupby('REAL') if name != 'DATE'] + 'x': df['DATE'], + 'customdata': df['REAL'], + 'y': df[vector], + 'name': name, + 'type': 'line' + } for name, df in data.groupby('REAL') if name != 'DATE'] layout = { - 'hovermode': 'closest', - 'barmode': 'overlay', - 'bargap': 0.05, - 'xaxis': {'title': 'Date', 'family': 'Equinor'}, - 'yaxis': {'title': vector, 'family': 'Equinor'}, - 'font': {'family': 'Equinor'}, - 'hoverlabel': {'font': {'family': 'Equinor'}}, - } + 'hovermode': 'closest', + 'barmode': 'overlay', + 'bargap': 0.05, + 'xaxis': {'title': 'Date', 'family': 'Equinor'}, + 'yaxis': {'title': vector, 'family': 'Equinor'}, + 'font': {'family': 'Equinor'}, + 'hoverlabel': {'font': {'family': 'Equinor'}}, + } return {'data': traces, 'layout': layout}