diff --git a/src/samplepack.cpp b/src/samplepack.cpp
--- a/src/samplepack.cpp
+++ b/src/samplepack.cpp
@@ -17,6 +17,7 @@
   along with serialplot.  If not, see .
 */
 
+#include 
 #include 
 
 #include "samplepack.h"
@@ -39,6 +40,15 @@ SamplePack::SamplePack(unsigned ns, unsi
     }
 }
 
+SamplePack::SamplePack(const SamplePack& other) :
+    SamplePack(other.numSamples(), other.numChannels(), other.hasX())
+{
+    size_t dataSize = sizeof(double) * numSamples();
+    if (hasX())
+        memcpy(xData(), other.xData(), dataSize);
+    memcpy(_yData, other._yData, dataSize * numChannels());
+}
+
 SamplePack::~SamplePack()
 {
     delete[] _yData;
diff --git a/src/samplepack.h b/src/samplepack.h
--- a/src/samplepack.h
+++ b/src/samplepack.h
@@ -29,6 +29,7 @@ public:
      * @param x has X channel
      */
     SamplePack(unsigned ns, unsigned nc, bool x = false);
+    SamplePack(const SamplePack& other);
     ~SamplePack();
 
     bool hasX() const;
diff --git a/tests/test.cpp b/tests/test.cpp
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -55,6 +55,30 @@ TEST_CASE("samplepack with X", "[memory]
     REQUIRE(pack.xData() != nullptr);
 }
 
+TEST_CASE("samplepack copy", "[memory]")
+{
+    SamplePack pack(10, 3, true);
+
+    // fill test data
+    for (int i = 0; i < 10; i++)
+    {
+        pack.xData()[i] = i;
+        pack.data(0)[i] = i+5;
+        pack.data(1)[i] = i*2;
+        pack.data(2)[i] = i*3;
+    }
+
+    SamplePack other = pack;
+    // compare
+    for (int i = 0; i < 10; i++)
+    {
+        REQUIRE(other.xData()[i] == i);
+        REQUIRE(other.data(0)[i] == i+5);
+        REQUIRE(other.data(1)[i] == i*2);
+        REQUIRE(other.data(2)[i] == i*3);
+    }
+}
+
 TEST_CASE("sink", "[memory, stream]")
 {
     TestSink sink;