diff --git a/README.md b/README.md
index e77fb6babf1310336fd10bb1428223596208a54d..44869c1164b9cc6a336bdbc444b2bee36a76edbf 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,12 @@ $ pip3 install pymagglobal --extra-index-url https://public:5mz_iyigu-WE3HySBH1J
 Since [conda](https://docs.conda.io/) version 4.6, conda and pip get along well. So you can also run `pip3 install ...` from inside your conda environment.
 
 ## Documentation
-Check out the extended documention [here](https://sec23.git-pages.gfz-potsdam.de/korte/pymagglobal). From the command line, you can use `pymagglobal` to get various results from the models. For example,
+Check out the extended documention [here](https://sec23.git-pages.gfz-potsdam.de/korte/pymagglobal).
+pymagglobal comes with a GUI, that can be started from the command line via
+```console
+$ pymagglobal-gui
+```
+You can also use pymagglobal directly from the command line, to get various results from the models. For example,
 ```console
 $ pymagglobal dipole gufm1
 ```
@@ -85,26 +90,19 @@ We can first use `built_in_models`, to access a dictionary of available models:
 ```python
 models = built_in_models()
 ```
-Using the function `file2splines` you can get a spline object, representing the model. For example, to get a spline object for gufm1, use
-```python
-gufm1_splines = pymagglobal.file2splines(models['gufm1'])
-```
-This object can be evaluated to get the coefficients for a specific epoch
+pymagglobal provides a `Model` class. Built-in models can be accessed directly, custom models are set up with a name and a path:
 ```python
-gufm1_1600 = gufm1_splines(1600)
+gufm1 = pymagglobal.Model('gufm1')
+my_model = pymagglobal.Model('My model', '<path/to/my_model.dat>')
 ```
-or passed to other routines in pymagglobal. For example, to get the dipole series from above use
+The model can be passed to routines in pymagglobal. For example, to get the dipole series from above use
 ```python
 import numpy as np
 
 times = np.linspace(1590, 1990, 201)
-gufm1_dipoles = pymagglobal.dipole_series(times, gufm1_splines)
-```
-Additionally, pymagglobal provides a `Model` class, which is set up with a path and a name:
-```python
-gufm1 = pymagglobal.Model('gufm1', models['gufm1'])
+gufm1_dipoles = pymagglobal.dipole_series(times, gufm1)
 ```
-The object now contains several quantities of interest, for example the minimal and maximal time for which the model is valid
+Additionally, the object contains several quantities of interest, for example the minimal and maximal time for which the model is valid
 ```python
 >>> gufm1.t_min
 1590.0
diff --git a/docs/index.rst b/docs/index.rst
index 8b26d86c1c18819c45b7f176efc11eea491396a8..3d538e62c779fa7f00f2f9b9781f56f32cae31ff 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,22 +1,20 @@
 A python interface for global geomagnetic field models
 ======================================================
 
-`pymagglobal` serves the purpose of replacing some Fortran scripts, which are used in the geomagnetism community to evaluate global field models.
+:code:`pymagglobal` serves the purpose of replacing some Fortran scripts, which are used in the geomagnetism community to evaluate global field models.
 It can be applied to all cubic-spline based geomagnetic field models stored in the same file format as `gufm1` or the `CALSxk` model series.
 
-.. toctree::
-   :maxdepth: 2
+:code:`pymagglobal` comes with a GUI, that can be started from the command line via
 
-   package_documentation
-   command_line_interface
-   examples
-   CHANGELOG
+.. code-block:: bash
+
+  $ pymagglobal-gui
 
 
 Installation
 ------------
 .. note::
-  pymagglobal depends on `cartopy <https://scitools.org.uk/cartopy>`_. You have to install it, before running the install command.   
+:code:`pymagglobal` depends on :code:`cartopy <https://scitools.org.uk/cartopy>`_. You have to install it, before running the install command.   
   This should also help if you receive :code:`ImportError: NumPy 1.10+ is required to install cartopy.` 
 
 
diff --git a/pymagglobal/GUI.py b/pymagglobal/GUI.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee48376889d17d19349ee2aba4ba57e53a8ae652
--- /dev/null
+++ b/pymagglobal/GUI.py
@@ -0,0 +1,554 @@
+# This file is part of pymagglobal
+#
+# Copyright (C) 2020 Helmholtz Centre Potsdam
+# GFZ German Research Centre for Geosciences, Potsdam, Germany
+# (https://www.gfz-potsdam.de)
+#
+# pymagglobal is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pymagglobal is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+'''This is a graphic interface for pymagglobal based on pyQt5 widgets
+'''
+
+import sys
+
+from pathlib import Path
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, \
+    QLineEdit, QVBoxLayout, QHBoxLayout, QComboBox, QFrame, QFileDialog, \
+    QRadioButton
+from PyQt5.QtGui import QDoubleValidator, QValidator
+
+import matplotlib
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, \
+    NavigationToolbar2QT
+from matplotlib import pyplot as plt
+from cartopy import crs as ccrs
+
+from pymagglobal import built_in_models, Model
+from pymagglobal.__main__ import argument_parser
+from pymagglobal._commands import local_curve, maps, dipole_series
+
+matplotlib.use('Qt5Agg')
+
+plt.rcParams.update({'font.size': 16})
+
+titleStyle = 'font-size: 18pt; font-weight: bold;'
+
+
+class MainWindow(QWidget):
+    def __init__(self):
+        super().__init__()
+        self.parser = argument_parser()
+        self.saveWindow = SaveWindow()
+
+        self.initUI()
+        self.updateModel()
+        self.plotDipole()
+
+    def initUI(self):
+        ''' Set up the layout and positions of menus and plots on the window.
+        '''
+        # create vertical layout for the parameters
+        paramBox = QVBoxLayout()
+        # add 'model selection' panel
+        paramBox.addWidget(self.modelSelect())
+        # add seperation line
+        paramBox.addWidget(QHLine())
+        # add'site parameters' panel
+        paramBox.addWidget(self.localMenu())
+        # add seperation line
+        paramBox.addWidget(QHLine())
+        # add 'global calculate' panel
+        paramBox.addWidget(self.globalMenu())
+        # add seperation line
+        paramBox.addWidget(QHLine())
+        # add 'save output' panel
+        paramBox.addWidget(self.saveMenu())
+        paramBox.setAlignment(Qt.AlignTop)
+
+        # put the vertical layout paramBox into the widget paramWidget
+        paramWidget = QWidget()
+        paramWidget.setLayout(paramBox)
+        paramWidget.setFixedWidth(300)
+        paramWidget.move(0, 0)
+
+        # create plotting environment
+        self.fig = plt.figure()
+        self.canvas = FigureCanvasQTAgg(self.fig)
+
+        # make the background transparent
+        self.fig.patch.set_facecolor('None')
+        self.canvas.setStyleSheet('background-color: transparent;')
+
+        # add the typical mpl toolbar
+        toolbar = NavigationToolbar2QT(self.canvas, self)
+
+        # put the plotting environment into a widget
+        plotBox = QVBoxLayout()
+        plotBox.addWidget(toolbar)
+        plotBox.addWidget(self.canvas)
+        plotWidget = QWidget()
+        plotWidget.setLayout(plotBox)
+
+        # combine the parameter and plotting widget into a global one
+        mwBox = QHBoxLayout()
+        mwBox.addWidget(paramWidget)
+        mwBox.addWidget(plotWidget)
+
+        # set up the main widget
+        self.setLayout(mwBox)
+        self.setGeometry(300, 300, 1300, 900)
+        self.setWindowTitle('pymagglobal')
+        self.show()
+
+    def updateModel(self):
+        ''' updates the model, when the selected model changes '''
+        model = Model(self.modelSelector.currentText())
+        # update the range
+        self.epoch.min = model.t_min
+        self.epoch.min = model.t_max
+        self.epoch.default = int((model.t_max + model.t_min) / 2)
+        self.range.setText(f'[{model.t_min}, {model.t_max}]')
+        # update the model for saving
+        self.saveWindow.model = model
+
+    def modelSelect(self):
+        ''' Model selection widget  '''
+        # title string
+        title = QLabel('Select model')
+        title.setStyleSheet(titleStyle)
+        title.setAlignment(Qt.AlignCenter)
+        # drop down menu
+        self.modelSelector = QComboBox()
+        self.modelSelector.addItems(built_in_models().keys())
+        self.modelSelector.currentIndexChanged.connect(self.updateModel)
+        self.modelSelector.currentIndexChanged.connect(self.plotDipole)
+        self.modelSelector.setFixedHeight(25)
+        # HOTFIX: this fixed scaling issues with the text...
+        self.modelSelector.setStyleSheet("QAbstractItemView {"
+                                         "  selection-background-color:"
+                                         "    lightgray;"
+                                         "}")
+        # show the model range
+        rangeLabel = QLabel('Time range (yrs.):')
+        rangeLabel.setAlignment(Qt.AlignCenter)
+        self.range = QLabel()
+        self.range.setAlignment(Qt.AlignCenter)
+        # put into widget
+        hbox = QVBoxLayout()
+        hbox.addWidget(title)
+        hbox.addWidget(self.modelSelector)
+        hbox.addWidget(rangeLabel)
+        hbox.addWidget(self.range)
+        hbox.setAlignment(Qt.AlignTop)
+        # return a widget containing the selector
+        q = QWidget()
+        q.setLayout(hbox)
+        q.setFixedHeight(140)
+        return q
+
+    def localMenu(self):
+        ''' Local plot widget '''
+        # title string
+        title = QLabel('Local plots')
+        title.setStyleSheet(titleStyle)
+        title.setAlignment(Qt.AlignCenter)
+        # lat and lon input fields
+        latLonLabel = QLabel('Position (lat, lon)')
+        latLonLabel.setAlignment(Qt.AlignCenter)
+
+        self.lat = QRangeEdit('0', -90, 90)
+        self.lat.setFixedWidth(50)
+        self.lat.setFixedHeight(22)
+        self.lon = QRangeEdit('0', -180, 180)
+        self.lon.setFixedWidth(50)
+        self.lon.setFixedHeight(22)
+        # set up a layout (looks nicer than adding the widgets directly)
+        latLonBox = QHBoxLayout()
+        latLonBox.addWidget(self.lat)
+        latLonBox.addWidget(self.lon)
+        latLonWidget = QWidget()
+        latLonWidget.setLayout(latLonBox)
+        # radio buttons for field type
+        typeLabel = QLabel('Field type')
+        typeLabel.setAlignment(Qt.AlignCenter)
+
+        self.locNezButton = QRadioButton("NEZ")
+        self.locDifButton = QRadioButton("DIF")
+        self.locDifButton.setChecked(True)
+        # set up a layout
+        typeBox = QHBoxLayout()
+        typeBox.addWidget(self.locNezButton)
+        typeBox.addWidget(self.locDifButton)
+        typeBox.setAlignment(Qt.AlignCenter)
+        typeWidget = QWidget()
+        typeWidget.setLayout(typeBox)
+        # Plot button
+        sButton = QPushButton('Plot local')
+        sButton.clicked.connect(self.plotLocal)
+        # Put into widget
+        vbox = QVBoxLayout()
+        vbox.addWidget(title)
+        vbox.addWidget(latLonLabel)
+        vbox.addWidget(latLonWidget)
+        vbox.addWidget(typeLabel)
+        vbox.addWidget(typeWidget)
+        vbox.addWidget(sButton)
+        vbox.setAlignment(Qt.AlignTop)
+        # return a widget containing the local stuff
+        q = QWidget()
+        q.setLayout(vbox)
+        q.setFixedHeight(240)
+        return q
+
+    def globalMenu(self):
+        ''' Global plot widget '''
+        # title string
+        title = QLabel('Global plots')
+        title.setStyleSheet(titleStyle)
+        title.setAlignment(Qt.AlignCenter)
+        # epoch input field
+        epochLabel = QLabel('Epoch [yrs.]')
+        epochLabel.setAlignment(Qt.AlignCenter)
+        self.epoch = QRangeEdit('1900', -1000, 2000)
+        self.epoch.setFixedWidth(100)
+        self.epoch.setFixedHeight(22)
+        # radio buttons for field type
+        typeLabel = QLabel('Field type')
+        typeLabel.setAlignment(Qt.AlignCenter)
+
+        self.mapNezButton = QRadioButton("NEZ")
+        self.mapDifButton = QRadioButton("DIF")
+        self.mapDifButton.setChecked(True)
+
+        typeBox = QHBoxLayout()
+        typeBox.addWidget(self.mapNezButton)
+        typeBox.addWidget(self.mapDifButton)
+        typeBox.setAlignment(Qt.AlignCenter)
+        typeWidget = QWidget()
+        typeWidget.setLayout(typeBox)
+        # redundant, but looks better as the location selector is in a hbox
+        epochBox = QHBoxLayout()
+        epochBox.addWidget(self.epoch)
+        epochWidget = QWidget()
+        epochWidget.setLayout(epochBox)
+
+        # buttons for map and dipole
+        gButton = QPushButton('Plot map')
+        gButton.clicked.connect(self.plotMap)
+
+        dButton = QPushButton('Plot dipole')
+        dButton.clicked.connect(self.plotDipole)
+        # put into widget
+        vbox = QVBoxLayout()
+        vbox.addWidget(title)
+        vbox.addWidget(epochLabel)
+        vbox.addWidget(epochWidget)
+        vbox.addWidget(typeLabel)
+        vbox.addWidget(typeWidget)
+        vbox.addWidget(gButton)
+        vbox.addWidget(dButton)
+        vbox.setAlignment(Qt.AlignTop)
+        # return a widget containing the global stuff 
+        q = QWidget()
+        q.setLayout(vbox)
+        q.setFixedHeight(270)
+        return q
+
+    def saveMenu(self):
+        ''' Save button widget '''
+        # title string
+        title = QLabel('Model output')
+        title.setStyleSheet(titleStyle)
+        title.setAlignment(Qt.AlignCenter)
+        # the button will open another window, which handles the output
+        gButton = QPushButton('Generate')
+        gButton.clicked.connect(self.saveWindow.show)
+
+        vbox = QVBoxLayout()
+        vbox.addWidget(title)
+        vbox.addWidget(gButton)
+        # return the save button widget
+        q = QWidget()
+        q.setLayout(vbox)
+        q.setFixedHeight(120)
+        return q
+
+    def plotLocal(self):
+        ''' This will update the plotting section, when the plot local button
+        is pressed. It basically turns the inputs into arguments for the CLI.
+        '''
+        # clear the figure
+        self.fig.clf()
+        # get the field type
+        if self.locDifButton.isChecked():
+            _type = 'dif'
+        else:
+            _type = 'nez'
+        # run the _command with the respective arguments
+        local_curve(self.parser.parse_args(['local',
+                                            '--type',
+                                            _type,
+                                            self.lat.text(),
+                                            self.lon.text(),
+                                            self.modelSelector.currentText()]),
+                    fig=self.fig)
+        # update the plotting canvas
+        self.canvas.draw()
+
+    def plotMap(self):
+        ''' This will update the plotting section, when the plot map button
+        is pressed. It basically turns the inputs into arguments for the CLI.
+        '''
+        # clear the figure
+        self.fig.clf()
+        # set up and configure grid for the maps
+        self.fig.add_subplot(221, projection=ccrs.Mollweide())
+        self.fig.add_subplot(222, projection=ccrs.Mollweide())
+        self.fig.add_subplot(212, projection=ccrs.Mollweide())
+        self.fig.tight_layout()
+        self.fig.subplots_adjust(top=0.9, bottom=0.1, hspace=0.4)
+        # get the field type
+        if self.mapDifButton.isChecked():
+            _type = 'dif'
+        else:
+            _type = 'nez'
+        # run the _command with the respective arguments
+        maps(self.parser.parse_args(['map',
+                                     '--type',
+                                     _type,
+                                     self.epoch.text(),
+                                     self.modelSelector.currentText()]),
+             fig=self.fig)
+        # update the canvas
+        self.canvas.draw()
+
+    def plotDipole(self):
+        ''' This will update the plotting section, when the plot dipole button
+        is pressed. It basically turns the inputs into arguments for the CLI.
+        '''
+        self.fig.clf()
+        dipole_series(self.parser.parse_args(['dipole',
+                                              self.modelSelector
+                                              .currentText()]),
+                      fig=self.fig)
+        self.canvas.draw()
+
+
+class SaveWindow(QWidget):
+
+    def __init__(self):
+        ''' This is another window, handling the output '''
+        super().__init__()
+        # set up a custom parser
+        self.parser = argument_parser()
+        # set up an initial model
+        self.model = Model('gufm1')
+        # configure the window
+        self.resize(300, 300)
+        self.setWindowTitle('pymagglobal - output')
+        # set up a dict for the dropdown menu
+        self.selectorDict = {'model coefficients at an epoch': 'coeffs-epoch',
+                             'field values at a specific location': 'local'}
+        # drop down menu of what to store
+        self.typeSelector = QComboBox()
+        self.typeSelector.addItems(self.selectorDict.keys())
+        self.typeSelector.currentIndexChanged.connect(self.updateInputBox)
+        # title string
+        info = QLabel('I want to store')
+        info.setAlignment(Qt.AlignLeft)
+        # dummy widget, will be updated according to the dropdown
+        self.inputWidget = QLabel()
+        # a button for storing
+        gButton = QPushButton('Store')
+        gButton.clicked.connect(self.save)
+
+        self.swBox = QVBoxLayout()
+        self.swBox.addWidget(info)
+        self.swBox.addWidget(self.typeSelector)
+        self.swBox.addWidget(self.inputWidget)
+
+        self.swBox.addWidget(gButton)
+
+        self.swBox.setAlignment(Qt.AlignTop)
+
+        # set up the main widget
+        self.setLayout(self.swBox)
+        self.updateInputBox()
+
+    def save(self):
+        ''' This will assemble the selections and pass them to the
+        argument_parser '''
+        # get the type of output
+        selected = self.selectorDict[self.typeSelector.currentText()]
+        # depending on the type of output, assemble the arguments
+        if selected == 'local':
+            if self.saveDifButton.isChecked():
+                _type = 'dif'
+            else:
+                _type = 'nez'
+            params = '--type ' + _type + ' ' + self.lat.text() + ' ' \
+                + self.lon.text()
+        elif selected == 'coeffs-epoch':
+            params = self.epoch.text()
+        # get an ouput filename
+        fname, _ = QFileDialog.getSaveFileName(self, 'Choose filename',
+                                               str(Path.home()))
+        # fname will be empty, if the cancel button is pressed or the dialog
+        # is closed. In this case, go back to the window (do nothing)
+        # otherwise, pass the arguments to the parser and close
+        if fname != '':
+            parse = selected + ' --no-show --output ' + fname + ' ' + params \
+                + ' ' + self.model.name
+
+            args = self.parser.parse_args(parse.split())
+            args.func(args)
+            self.close()
+
+    def updateInputBox(self):
+        ''' This will update the argument inputs, depending on the dropdown
+        selection '''
+        # first clear the widget
+        self.inputWidget.deleteLater()
+        # get the input
+        selected = self.selectorDict[self.typeSelector.currentText()]
+        # according to the input, set up a new widget
+        if selected == 'local':
+            self.inputWidget = self.locSelector()
+        elif selected == 'coeffs-epoch':
+            self.inputWidget = self.epochSelector()
+        else:
+            self.inputWidget = QLabel(self.selectorDict[self.typeSelector
+                                                        .currentText()])
+        # insert the widget after the dropdown (position 3)
+        self.swBox.insertWidget(3, self.inputWidget)
+        # update
+        self.swBox.update()
+        self.updateGeometry()
+
+    def locSelector(self):
+        ''' The argument widget for local output '''
+        latLonLabel = QLabel('Position (lat, lon)')
+        latLonLabel.setAlignment(Qt.AlignCenter)
+
+        self.lat = QRangeEdit('0', -90, 90)
+        self.lat.setFixedWidth(50)
+        self.lon = QRangeEdit('0', -180, 180)
+        self.lon.setFixedWidth(50)
+
+        latLonBox = QHBoxLayout()
+        latLonBox.addWidget(self.lat)
+        latLonBox.addWidget(self.lon)
+
+        latLonWidget = QWidget()
+        latLonWidget.setLayout(latLonBox)
+        # Radio buttons for field type
+        typeLabel = QLabel('Field type')
+        typeLabel.setAlignment(Qt.AlignCenter)
+
+        self.saveNezButton = QRadioButton("NEZ")
+        self.saveDifButton = QRadioButton("DIF")
+        self.saveDifButton.setChecked(True)
+
+        typeBox = QHBoxLayout()
+        typeBox.addWidget(self.saveNezButton)
+        typeBox.addWidget(self.saveDifButton)
+        typeBox.setAlignment(Qt.AlignCenter)
+        typeWidget = QWidget()
+        typeWidget.setLayout(typeBox)
+        # Put into widget
+        vbox = QVBoxLayout()
+        vbox.addWidget(latLonLabel)
+        vbox.addWidget(latLonWidget)
+        vbox.addWidget(typeLabel)
+        vbox.addWidget(typeWidget)
+        vbox.setAlignment(Qt.AlignTop)
+
+        q = QWidget()
+        q.setLayout(vbox)
+        return q
+
+    def epochSelector(self):
+        ''' The argument widget for coefficient output '''
+        epochLabel = QLabel('Epoch [yrs.]')
+        epochLabel.setAlignment(Qt.AlignCenter)
+
+        default = int((self.model.t_max + self.model.t_min) / 2)
+        self.epoch = QRangeEdit(str(default),
+                                self.model.t_min,
+                                self.model.t_max,
+                                default)
+        self.epoch.setFixedWidth(100)
+        # redundant, but looks better as the location selector is in a hbox
+        epochBox = QHBoxLayout()
+        epochBox.addWidget(self.epoch)
+        epochWidget = QWidget()
+        epochWidget.setLayout(epochBox)
+
+        # Put into widget
+        vbox = QVBoxLayout()
+        vbox.addWidget(epochLabel)
+        vbox.addWidget(epochWidget)
+        vbox.setAlignment(Qt.AlignTop)
+
+        q = QWidget()
+        q.setLayout(vbox)
+        return q
+
+
+class QHLine(QFrame):
+    ''' Horizontal separation lines on pyQt5 layout
+    '''
+    def __init__(self):
+        super(QHLine, self).__init__()
+        self.setFrameShape(QFrame.HLine)
+        self.setFrameShadow(QFrame.Sunken)
+
+
+class QRangeEdit(QLineEdit):
+    ''' A line edit that checks for input in a given range. If the input is
+    outside, a default value will be inserted. '''
+    def __init__(self, init, _min, _max, default=0):
+        super().__init__(init)
+        self.min = _min
+        self.max = _max
+        self.default = default
+
+        self.editingFinished.connect(self.validating)
+
+    def validating(self):
+        validation_rule = QDoubleValidator(self.min, self.max, 4)
+        if (validation_rule.validate(self.text(),
+                                     14)[0] == QValidator.Acceptable):
+            self.setFocus()
+        else:
+            self.setText(str(self.default))
+
+
+def main():
+    app = QApplication(sys.argv)
+    app.setStyleSheet("QLabel{font-size: 16pt;}"
+                      "QComboBox{font-size: 16pt;}"
+                      "QPushButton{font-size: 16pt;}"
+                      "QRadioButton{font-size: 16pt; height: 100px}"
+                      "QLineEdit{font-size: 16pt;}")
+
+    _ = MainWindow()
+
+    sys.exit(app.exec_())
+
+
+if __name__ == '__main__':
+    main()
diff --git a/pymagglobal/__init__.py b/pymagglobal/__init__.py
index 94747ccf6e91816f0656ba84e4d4ce3e3f1913f2..ea1c7be253fa89bef7e59f246d1d934830067d23 100644
--- a/pymagglobal/__init__.py
+++ b/pymagglobal/__init__.py
@@ -46,4 +46,4 @@ from pymagglobal.core import local_curve, dipole_series, file2splines, \
     Model
 from pymagglobal import utils
 
-__version__ = '0.1.4'
+__version__ = '1.0.0'
diff --git a/pymagglobal/_commands.py b/pymagglobal/_commands.py
index 31d04dadb4fff3a62c4ede7d8d0caeaf528e7e4a..d848fd558a4cb17593ad2146be11a954ddfdffcc 100644
--- a/pymagglobal/_commands.py
+++ b/pymagglobal/_commands.py
@@ -121,7 +121,7 @@ def lt2yr(times):
     return -times*1000 + 1950
 
 
-def local_curve(args):
+def local_curve(args, fig=None):
     '''Handle the local command and create a local curve at a given
     location.
 
@@ -129,6 +129,8 @@ def local_curve(args):
     ----------
     args : object
         The SimpleNamespace object returned by ArgumentParser.parse_args().
+    fig : matplotlib.figure.Figure, optional
+        A figure to plot into. This is used for the GUI.
 
     Returns
     -------
@@ -142,7 +144,7 @@ def local_curve(args):
     # create a local curve using the core function, check is performed
     # in args2times
     curves = core.local_curve(times, (args.lat, args.lon), args.model,
-                               field_type=args.type, check=False)
+                              field_type=args.type, check=False)
     # output formats for dif and nez components
     fmts = {'dif': ('%.2f', '%2.6f', '%2.6f', '%1.7e'),
             'nez': ('%.2f', '%1.7e', '%1.7e', '%1.7e')}
@@ -169,7 +171,8 @@ def local_curve(args):
 
     # if the --no-show flag is not set, plot the local curve
     if not args.no_show or args.savefig is not None:
-        fig = plt.figure(figsize=(10, 7))
+        if fig is None:
+            fig = plt.figure(figsize=(10, 7))
         fig.suptitle(f'Local curves at ({args.lat}°, {args.lon}°) '
                      f'for {args.model.name}')
         axs = np.empty(3, dtype=object)
@@ -190,7 +193,7 @@ def local_curve(args):
         return fig
 
 
-def dipole_series(args):
+def dipole_series(args, fig=None):
     '''Handle the dipole command and create a dipole-moment time series for
     the given model.
 
@@ -198,6 +201,8 @@ def dipole_series(args):
     ----------
     args : object
         The object returned by ArgumentParser.parse_args().
+    fig : matplotlib.figure.Figure, optional
+        A figure to plot into. This is used for the GUI.
 
     Returns
     -------
@@ -228,7 +233,8 @@ def dipole_series(args):
 
     # if the --no-show flag is not set, plot the time series
     if not args.no_show or args.savefig is not None:
-        fig = plt.figure(figsize=(10, 7))
+        if fig is None:
+            fig = plt.figure(figsize=(10, 7))
         ax = fig.add_subplot(111)
         ax.plot(times, dip_ser)
         ax.set_title(f'Dipole moment series for {args.model.name}')
@@ -462,7 +468,7 @@ def plot_coeffs(gs, ls, ms, args, unit='nT', name=r'Coefficients $g_\ell^m$'):
     return fig
 
 
-def maps(args):
+def maps(args, fig=None):
     '''Handle the map command and create field map of the model for a given
     epoch.
 
@@ -470,6 +476,8 @@ def maps(args):
     ----------
     args : object
         the object returned by ArgumentParser.parse_args().
+    fig : matplotlib.figure.Figure, optional
+        A figure to plot into. This is used for the GUI.
 
     Returns
     -------
@@ -507,49 +515,72 @@ def maps(args):
 
     # if the --no-show flag is not set, plot the map using cartopy
     if not args.no_show or args.savefig is not None:
-        vmaxs = np.max(np.abs(field), axis=1)
-        vmins = -vmaxs
         cmaps = ['RdBu', 'RdBu', 'RdBu']
         units = [r'$\mu$T', r'$\mu$T', r'$\mu$T']
+        levels = [10, 10, 10]
+        extends = ['neither', 'neither', 'neither']
         if args.type == 'dif':
             field[2] /= 1000
-            vmaxs[0] = 40
-            vmins[0] = -vmaxs[0]
-            vmaxs[2] /= 1000
-            vmins[2] = np.min(field[2])
+
+            levels[0] = [-40, -30, -20, -10, 0, 10, 20, 30, 40]
+            levels[1] = np.linspace(-90, 90, 11)
+            extends[0] = 'both'
             cmaps[2] = 'Blues'
             units[0] = r'deg.'
             units[1] = r'deg.'
         else:
-            vmaxs /= 1000
-            vmins /= 1000
             field /= 1000
+            vmax = np.ceil(np.max(np.abs(field), axis=1))
+
+            for it in range(3):
+                levels[it] = np.linspace(-vmax[it], vmax[it], 11)
+
+        cbar_hght = 0.08
+        if fig is None:
+            proj = ccrs.Mollweide()
+            fig, axs = plt.subplots(1, 3, figsize=(13, 3.4),
+                                    subplot_kw={'projection': proj})
+            fig.tight_layout()
+            fig.subplots_adjust(top=0.8, bottom=0.25, wspace=0.1)
+            colaxs = []
+            for it in range(3):
+                bnds = axs[it].get_position().bounds
+
+                colaxs.append(fig.add_axes([bnds[0],
+                                            bnds[1]-0.06-cbar_hght,
+                                            bnds[2],
+                                            cbar_hght]))
+            cbar_orientation = 'horizontal'
+        else:
+            axs = fig.get_axes()
+            proj = axs[0].projection
+
+            colaxs = []
+            for it in range(3):
+                bnds = axs[it].get_position().bounds
+                colaxs.append(fig.add_axes([bnds[0],
+                                            bnds[1]-0.02-0.5*cbar_hght,
+                                            bnds[2],
+                                            0.5*cbar_hght]))
+            cbar_orientation = 'horizontal'
 
-        cbar_hght = 0.06
-        proj = ccrs.Mollweide()
         plt_lat, plt_lon, _ = proj.transform_points(ccrs.Geodetic(),
                                                     z_at[1],
                                                     90 - z_at[0]).T
 
-        fig, axs = plt.subplots(1, 3, figsize=(13, 3),
-                                subplot_kw={'projection': proj})
         fig.suptitle(f'Field maps for {args.model.name} at epoch '
                      f'{args.epoch} {args.t_unit}')
 
         for it in range(3):
-            mappable = axs[it].tripcolor(plt_lat, plt_lon, field[it],
-                                         vmin=vmins[it], vmax=vmaxs[it],
-                                         rasterized=True, cmap=cmaps[it])
+            mappable = axs[it].tricontourf(plt_lat, plt_lon, field[it],
+                                           cmap=cmaps[it],
+                                           levels=levels[it],
+                                           extend=extends[it])
             axs[it].coastlines(alpha=0.8, lw=0.5)
             axs[it].set_global()
             axs[it].set_title(f'{utils._names[args.type][it]} [{units[it]}]')
-            bnds = axs[it].get_position().bounds
-            # colorbar for the mean
-            colax = fig.add_axes([bnds[0],
-                                  bnds[1]-0.1-cbar_hght,
-                                  bnds[2],
-                                  cbar_hght])
+            colaxs[it].tick_params(labelsize=12)
             fig.colorbar(mappable,
-                         cax=colax,
-                         orientation='horizontal')
+                         cax=colaxs[it],
+                         orientation=cbar_orientation)
         return fig
diff --git a/setup.py b/setup.py
index 1e4f1ae24655bb07fb97630f5d6b47ea4d1d8817..c6dbf51511a88164faaa4d4a329520f89da1c011 100644
--- a/setup.py
+++ b/setup.py
@@ -44,8 +44,8 @@ with open("README.md", "r") as fh:
 name = 'pymagglobal'
 version = get_version('pymagglobal/__init__.py')
 description = '''python interface for global geomagnetic field models '''
-copyright = f'2020 Helmholtz Centre Potsdam GFZ, ' \
-    + f'German Research Centre for Geosciences, Potsdam, Germany'
+copyright = '2020 Helmholtz Centre Potsdam GFZ, ' \
+    + 'German Research Centre for Geosciences, Potsdam, Germany'
 
 
 setuptools.setup(
@@ -61,11 +61,14 @@ setuptools.setup(
         'numpy>=1.18',
         'scipy>=1.5.4',
         'matplotlib>=2.2.5',
-        'cartopy>=0.17'
+        'cartopy>=0.17',
+        'PyQt5>=5.12'
         ],
     extras_require={'tests': ['orthopoly>=0.9', 'packaging']},
     package_data={'pymagglobal': ['dat/*']},
     include_package_data=True,
-    entry_points={'console_scripts': [f'pymagglobal = '
-                                      f'pymagglobal.__main__:main']},
+    entry_points={'console_scripts': ['pymagglobal = '
+                                      'pymagglobal.__main__:main',
+                                      'pymagglobal-gui = '
+                                      'pymagglobal.GUI:main']},
 )