diff --git a/dune/vtk/filewriter.hh b/dune/vtk/filewriter.hh
index 217c79488900051f03aeda8967bed3792d9b4990..8873e75644fffecabdb1fc4b20026d8943c7e4ae 100644
--- a/dune/vtk/filewriter.hh
+++ b/dune/vtk/filewriter.hh
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <string>
+#include <dune/common/std/optional.hh>
 
 namespace Dune
 {
@@ -10,8 +11,8 @@ namespace Dune
     /// Virtual destructor
     virtual ~FileWriter () = default;
 
-    /// Write to file given by `filename`
-    virtual void write (std::string const& filename) const = 0;
+    /// Write to file given by `filename` and (optionally) store additional data in `dataDir`
+    virtual void write (std::string const& filename, Std::optional<std::string> dataDir = {}) const = 0;
   };
 
 } // end namespace Dune
diff --git a/dune/vtk/pvdwriter.hh b/dune/vtk/pvdwriter.hh
index 919cab9b54d417a392cf197dcef053f336a5bb69..46343bd01d10381c5b30da2379958de38f8f6ac8 100644
--- a/dune/vtk/pvdwriter.hh
+++ b/dune/vtk/pvdwriter.hh
@@ -34,14 +34,23 @@ namespace Dune
      * Create timestep files for the data associated to the current timestep `time`.
      *
      * \param time  The time value of the written data
-     * \param fn  Filename of the file to write to. Is stored in \ref timesteps_.
+     * \param fn    Filename of the PVD file to write to. The base part is used to
+     *              create filenames for the timestep files that are stored in \ref timesteps_.
+     *              May contain directory and any filename extension.
+     * \param dir   Specifies where to write the timestep files.
      * \param writeCollection  Create a collection .pvd file directly
      **/
-    void writeTimestep (double time, std::string const& fn, bool writeCollection = true) const;
+    void writeTimestep (double time, std::string const& fn, Std::optional<std::string> dir = {},
+                        bool writeCollection = true) const;
 
     /// Writes collection of timesteps to .pvd file.
     // NOTE: requires an aforging call to \ref writeTimestep
-    virtual void write (std::string const& fn) const override;
+    /**
+     * \param fn  The filename of the PVD file. May contain directory and any filename extension.
+     * \param dir (Ignored) Timestep files are already written and their filenames are
+     *            stored in \ref timesteps_.
+     **/
+    virtual void write (std::string const& fn, Std::optional<std::string> dir = {}) const override;
 
     /// Attach point data to the writer, \see VtkFunction for possible arguments
     template <class Function, class... Args>
diff --git a/dune/vtk/pvdwriter.impl.hh b/dune/vtk/pvdwriter.impl.hh
index ddafacc260e2757b4c022f8c17374767ae1da2af..8841309fe1fab4cf32b65e6768d139f5873f141d 100644
--- a/dune/vtk/pvdwriter.impl.hh
+++ b/dune/vtk/pvdwriter.impl.hh
@@ -9,30 +9,32 @@ namespace Dune {
 
 template <class W>
 void PvdWriter<W>
-  ::writeTimestep (double time, std::string const& fn, bool writeCollection) const
+  ::writeTimestep (double time, std::string const& fn, Std::optional<std::string> dir, bool writeCollection) const
 {
   auto p = filesystem::path(fn);
   auto name = p.stem();
   p.remove_filename();
-  p /= name.string();
+
+  filesystem::path fn_dir = p;
+  filesystem::path data_dir = dir ? filesystem::path(*dir) : fn_dir;
+  filesystem::path rel_dir = filesystem::relative(data_dir, fn_dir);
+
+  std::string pvd_fn = fn_dir.string() + '/' + name.string();
+  std::string seq_fn = data_dir.string() + '/' + name.string() + "_t" + std::to_string(timesteps_.size());
+  std::string rel_fn = rel_dir.string() + '/' + name.string() + "_t" + std::to_string(timesteps_.size());
 
   std::string ext = "." + vtkWriter_.getFileExtension();
-  std::string filename = p.string() + "_t" + std::to_string(timesteps_.size());
-
-  int rank = 0;
-  int num_ranks = 1;
-#ifdef HAVE_MPI
-  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
-  MPI_Comm_size(MPI_COMM_WORLD, &num_ranks);
-  if (num_ranks > 1)
+
+  int rank = vtkWriter_.rank_;
+  int numRanks = vtkWriter_.numRanks_;
+  if (numRanks > 1)
     ext = ".p" + vtkWriter_.getFileExtension();
-#endif
 
-  timesteps_.emplace_back(time, filename + ext);
-  vtkWriter_.write(filename + ext);
+  timesteps_.emplace_back(time, rel_fn + ext);
+  vtkWriter_.write(seq_fn + ext);
 
   if (rank == 0 && writeCollection) {
-    std::ofstream out(p.string() + ".pvd", std::ios_base::ate | std::ios::binary);
+    std::ofstream out(pvd_fn + ".pvd", std::ios_base::ate | std::ios::binary);
     assert(out.is_open());
 
     out.imbue(std::locale::classic());
@@ -47,20 +49,14 @@ void PvdWriter<W>
 
 template <class W>
 void PvdWriter<W>
-  ::write (std::string const& fn) const
+  ::write (std::string const& fn, Std::optional<std::string> /*dir*/) const
 {
   auto p = filesystem::path(fn);
   auto name = p.stem();
   p.remove_filename();
   p /= name.string();
 
-  int rank = 0;
-  int num_ranks = 1;
-#ifdef HAVE_MPI
-  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
-  MPI_Comm_size(MPI_COMM_WORLD, &num_ranks);
-#endif
-
+  int rank = vtkWriter_.rank_;
   if (rank == 0) {
     std::ofstream out(p.string() + ".pvd", std::ios_base::ate | std::ios::binary);
     assert(out.is_open());
diff --git a/dune/vtk/vtktimeserieswriter.hh b/dune/vtk/vtktimeserieswriter.hh
index a329f528cfdf0e0659fae069acf960df1f41124f..587467731857c948eeb8b2b9c69ee83c15b38402 100644
--- a/dune/vtk/vtktimeserieswriter.hh
+++ b/dune/vtk/vtktimeserieswriter.hh
@@ -58,16 +58,22 @@ namespace Dune
      * \param fn  Filename of the file to write to. Only the base part
      *            (without dir and extentsion) is used to write the intermediate
      *            file into a tmp directory.
+     * \param tmpDir  If the directory is given, it is used as tmp dir, otherwise /tmp.
      * \param writeCollection  Create a timeseries file directly
      **/
-    void writeTimestep (double time, std::string const& fn, bool writeCollection = true) const;
+    void writeTimestep (double time, std::string const& fn,
+                        Std::optional<std::string> tmpDir = {},
+                        bool writeCollection = true) const;
 
     /// Writes all timesteps to single timeseries file.
     // NOTE: requires an aforging call to \ref writeTimestep
     /**
      * Create a timeseries file with all timesteps written by \ref writeTimestep.
+     *
+     * \param fn   Filename of the Timeseries file. May contain a directory and any file extension.
+     * \param dir  The optional parameter specifies the directory of the partition files.
      **/
-    virtual void write (std::string const& fn) const override;
+    virtual void write (std::string const& fn, Std::optional<std::string> dir = {}) const override;
 
     /// Attach point data to the writer, \see VtkFunction for possible arguments
     template <class Function, class... Args>
diff --git a/dune/vtk/vtktimeserieswriter.impl.hh b/dune/vtk/vtktimeserieswriter.impl.hh
index b51ad6368c026b7db6f1f49bb451a2c69324750f..fb7d76266c457867023c462286358845b9b0ce57 100644
--- a/dune/vtk/vtktimeserieswriter.impl.hh
+++ b/dune/vtk/vtktimeserieswriter.impl.hh
@@ -9,7 +9,7 @@
 #include <sstream>
 #include <string>
 
-#ifdef HAVE_ZLIB
+#if HAVE_ZLIB
 #include <zlib.h>
 #endif
 
@@ -39,24 +39,20 @@ VtkTimeseriesWriter<W>::~VtkTimeseriesWriter ()
 
 template <class W>
 void VtkTimeseriesWriter<W>
-  ::writeTimestep (double time, std::string const& fn, bool writeCollection) const
+  ::writeTimestep (double time, std::string const& fn, Std::optional<std::string> tmpDir, bool writeCollection) const
 {
   auto name = filesystem::path(fn).stem();
-  auto tmp = tmpDir_;
+  auto tmp = tmpDir ? filesystem::path(*tmpDir) : tmpDir_;
   tmp /= name.string();
 
   vtkWriter_.dataCollector_.update();
 
   std::string filenameBase = tmp.string();
 
-  int rank = 0;
-  int num_ranks = 1;
-  #ifdef HAVE_MPI
-    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
-    MPI_Comm_size(MPI_COMM_WORLD, &num_ranks);
-    if (num_ranks > 1)
-      filenameBase = tmp.string() + "_p" + std::to_string(rank);
-  #endif
+  int rank = vtkWriter_.rank_;
+  int numRanks = vtkWriter_.numRanks_;
+  if (numRanks > 1)
+    filenameBase = tmp.string() + "_p" + std::to_string(rank);
 
   if (!initialized_) {
     // write points and cells only once
@@ -78,28 +74,29 @@ void VtkTimeseriesWriter<W>
 
 template <class W>
 void VtkTimeseriesWriter<W>
-  ::write (std::string const& fn) const
+  ::write (std::string const& fn, Std::optional<std::string> dir) const
 {
   assert( initialized_ );
 
   auto p = filesystem::path(fn);
   auto name = p.stem();
   p.remove_filename();
-  p /= name.string();
 
-  std::string filename = p.string() + "_ts";
+  filesystem::path fn_dir = p;
+  filesystem::path data_dir = dir ? filesystem::path(*dir) : fn_dir;
+  filesystem::path rel_dir = filesystem::relative(data_dir, fn_dir);
 
-  int rank = 0;
-  int num_ranks = 1;
-#ifdef HAVE_MPI
-    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
-    MPI_Comm_size(MPI_COMM_WORLD, &num_ranks);
-    if (num_ranks > 1)
-      filename = p.string() + "_ts_p" + std::to_string(rank);
-#endif
+  std::string serial_fn = fn_dir.string() + '/' + name.string() + "_ts";
+  std::string parallel_fn = data_dir.string() + '/' + name.string() + "_ts";
+  std::string rel_fn = rel_dir.string() + '/' + name.string() + "_ts";
+
+  int rank = vtkWriter_.rank_;
+  int numRanks = vtkWriter_.numRanks_;
+  if (numRanks > 1)
+    serial_fn += "_p" + std::to_string(rank);
 
   { // write serial file
-    std::ofstream serial_out(filename + "." + vtkWriter_.getFileExtension(),
+    std::ofstream serial_out(serial_fn + "." + vtkWriter_.getFileExtension(),
                              std::ios_base::ate | std::ios::binary);
     assert(serial_out.is_open());
 
@@ -111,10 +108,9 @@ void VtkTimeseriesWriter<W>
     vtkWriter_.writeTimeseriesSerialFile(serial_out, filenameMesh_, timesteps_, blocks_);
   }
 
-#ifdef HAVE_MPI
-  if (num_ranks > 1 && rank == 0) {
+  if (numRanks > 1 && rank == 0) {
     // write parallel file
-    std::ofstream parallel_out(p.string() + "_ts.p" + vtkWriter_.getFileExtension(),
+    std::ofstream parallel_out(parallel_fn + ".p" + vtkWriter_.getFileExtension(),
                                std::ios_base::ate | std::ios::binary);
     assert(parallel_out.is_open());
 
@@ -123,9 +119,8 @@ void VtkTimeseriesWriter<W>
       ? std::numeric_limits<float>::digits10+2
       : std::numeric_limits<double>::digits10+2);
 
-    vtkWriter_.writeTimeseriesParallelFile(parallel_out, p.string() + "_ts", num_ranks, timesteps_);
+    vtkWriter_.writeTimeseriesParallelFile(parallel_out, rel_fn, numRanks, timesteps_);
   }
-#endif
 }
 
 } // end namespace Dune
diff --git a/dune/vtk/vtkwriterinterface.hh b/dune/vtk/vtkwriterinterface.hh
index 9f1b3866704c1e025c414b4e9e29629ad91511b1..ca63534bdd6f7986541af6f50fa1383ba816a0e5 100644
--- a/dune/vtk/vtkwriterinterface.hh
+++ b/dune/vtk/vtkwriterinterface.hh
@@ -44,10 +44,12 @@ namespace Dune
                         Vtk::FormatTypes format = Vtk::BINARY,
                         Vtk::DataTypes datatype = Vtk::FLOAT32)
       : dataCollector_(gridView)
+      , rank_(gridView.comm().rank())
+      , numRanks_(gridView.comm().size())
       , format_(format)
       , datatype_(datatype)
     {
-#ifndef HAVE_ZLIB
+#if !HAVE_ZLIB
       if (format_ == Vtk::COMPRESSED) {
         std::cout << "Dune is compiled without compression. Falling back to BINARY VTK output!\n";
         format_ = Vtk::BINARY;
@@ -56,7 +58,11 @@ namespace Dune
     }
 
     /// Write the attached data to the file
-    virtual void write (std::string const& fn) const override;
+    /**
+     * \param fn   Filename of the VTK file. May contain a directory and any file extension.
+     * \param dir  The optional parameter specifies the directory of the partition files.
+     **/
+    virtual void write (std::string const& fn, Std::optional<std::string> dir = {}) const override;
 
     /// Attach point data to the writer, \see VtkFunction for possible arguments
     template <class Function, class... Args>
@@ -151,6 +157,10 @@ namespace Dune
   protected:
     mutable DataCollector dataCollector_;
 
+    // the rank and size of the gridView collective communication
+    int rank_ = 0;
+    int numRanks_ = 1;
+
     Vtk::FormatTypes format_;
     Vtk::DataTypes datatype_;
 
diff --git a/dune/vtk/vtkwriterinterface.impl.hh b/dune/vtk/vtkwriterinterface.impl.hh
index e5ed55f6ab56628cf23bf85a72c3adcb072d4f5c..ce08aa8af8a746451569b3511350d0dfecb07aa2 100644
--- a/dune/vtk/vtkwriterinterface.impl.hh
+++ b/dune/vtk/vtkwriterinterface.impl.hh
@@ -8,7 +8,7 @@
 #include <sstream>
 #include <string>
 
-#ifdef HAVE_ZLIB
+#if HAVE_ZLIB
 #include <zlib.h>
 #endif
 
@@ -23,29 +23,27 @@ namespace Dune {
 
 template <class GV, class DC>
 void VtkWriterInterface<GV,DC>
-  ::write (std::string const& fn) const
+  ::write (std::string const& fn, Std::optional<std::string> dir) const
 {
   dataCollector_.update();
 
   auto p = filesystem::path(fn);
   auto name = p.stem();
   p.remove_filename();
-  p /= name.string();
 
-  std::string filename = p.string();
+  filesystem::path fn_dir = p;
+  filesystem::path data_dir = dir ? filesystem::path(*dir) : fn_dir;
+  filesystem::path rel_dir = filesystem::relative(data_dir, fn_dir);
 
-  int rank = 0;
-  int num_ranks = 1;
+  std::string serial_fn = fn_dir.string() + '/' + name.string();
+  std::string parallel_fn = data_dir.string() + '/' + name.string();
+  std::string rel_fn = rel_dir.string() + '/' + name.string();
 
-#ifdef HAVE_MPI
-  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
-  MPI_Comm_size(MPI_COMM_WORLD, &num_ranks);
-  if (num_ranks > 1)
-    filename = p.string() + "_p" + std::to_string(rank);
-#endif
+  if (numRanks_ > 1)
+    serial_fn += "_p" + std::to_string(rank_);
 
   { // write serial file
-    std::ofstream serial_out(filename + "." + fileExtension(), std::ios_base::ate | std::ios::binary);
+    std::ofstream serial_out(serial_fn + "." + fileExtension(), std::ios_base::ate | std::ios::binary);
     assert(serial_out.is_open());
 
     serial_out.imbue(std::locale::classic());
@@ -56,10 +54,9 @@ void VtkWriterInterface<GV,DC>
     writeSerialFile(serial_out);
   }
 
-#ifdef HAVE_MPI
-  if (num_ranks > 1 && rank == 0) {
+  if (numRanks_ > 1 && rank_ == 0) {
     // write parallel file
-    std::ofstream parallel_out(p.string() + ".p" + fileExtension(), std::ios_base::ate | std::ios::binary);
+    std::ofstream parallel_out(parallel_fn + ".p" + fileExtension(), std::ios_base::ate | std::ios::binary);
     assert(parallel_out.is_open());
 
     parallel_out.imbue(std::locale::classic());
@@ -67,9 +64,8 @@ void VtkWriterInterface<GV,DC>
       ? std::numeric_limits<float>::digits10+2
       : std::numeric_limits<double>::digits10+2);
 
-    writeParallelFile(parallel_out, p.string(), num_ranks);
+    writeParallelFile(parallel_out, rel_fn, numRanks_);
   }
-#endif
 }
 
 
@@ -220,7 +216,7 @@ template <class OStream>
 std::uint64_t writeCompressed (unsigned char const* buffer, unsigned char* buffer_out,
                                std::uint64_t bs, std::uint64_t cbs, int level, OStream& outb)
 {
-#ifdef HAVE_ZLIB
+#if HAVE_ZLIB
   uLongf uncompressed_space = uLongf(bs);
   uLongf compressed_space = uLongf(cbs);
 
diff --git a/src/timeserieswriter.cc b/src/timeserieswriter.cc
index 73282db04c076fcf683e7e75da894a4b743416ae..e8ac785add8f210d0f31b5847aee33f6e70662c5 100644
--- a/src/timeserieswriter.cc
+++ b/src/timeserieswriter.cc
@@ -34,7 +34,7 @@ void write (std::string prefix, GridView const& gridView)
   seriesWriter.addCellData(p1Analytic, "q0");
   std::string filename = prefix + "_" + std::to_string(GridView::dimensionworld) + "d_binary32.vtu";
   for (double t = 0.0; t < 5; t += 0.5) {
-    seriesWriter.writeTimestep(t, filename, false);
+    seriesWriter.writeTimestep(t, filename, {}, false);
     shift += 0.25;
   }
   seriesWriter.write(filename);