diff --git a/Cargo.lock b/Cargo.lock
index f5eb784dce45f0dcde835c39150678590708649d..3013fbae22c3822c4e080096db6c7633499c5b98 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -461,6 +461,12 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
 [[package]]
 name = "bit-set"
 version = "0.6.0"
@@ -661,6 +667,12 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
 [[package]]
 name = "com"
 version = "0.6.0"
@@ -761,6 +773,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "core_maths"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
+dependencies = [
+ "libm",
+]
+
 [[package]]
 name = "cpufeatures"
 version = "0.2.16"
@@ -1013,7 +1034,7 @@ dependencies = [
  "enum-map",
  "log",
  "mime_guess2",
- "resvg",
+ "resvg 0.37.0",
 ]
 
 [[package]]
@@ -1258,6 +1279,29 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
 
+[[package]]
+name = "fontconfig-parser"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
+dependencies = [
+ "roxmltree 0.20.0",
+]
+
+[[package]]
+name = "fontdb"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3a6f9af55fb97ad673fb7a69533eb2f967648a06fa21f8c9bb2cd6d33975716"
+dependencies = [
+ "fontconfig-parser",
+ "log",
+ "memmap2",
+ "slotmap",
+ "tinyvec",
+ "ttf-parser 0.24.1",
+]
+
 [[package]]
 name = "foreign-types"
 version = "0.5.0"
@@ -1427,6 +1471,16 @@ dependencies = [
  "wasi",
 ]
 
+[[package]]
+name = "gif"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
 [[package]]
 name = "gimli"
 version = "0.31.1"
@@ -1804,12 +1858,28 @@ dependencies = [
  "png",
 ]
 
+[[package]]
+name = "image-webp"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
+dependencies = [
+ "byteorder-lite",
+ "quick-error",
+]
+
 [[package]]
 name = "imagesize"
 version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
 
+[[package]]
+name = "imagesize"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
+
 [[package]]
 name = "immutable-chunkmap"
 version = "2.0.6"
@@ -1920,6 +1990,16 @@ dependencies = [
  "arrayvec",
 ]
 
+[[package]]
+name = "kurbo"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
+dependencies = [
+ "arrayvec",
+ "smallvec",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.5.0"
@@ -1942,6 +2022,12 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "libm"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+
 [[package]]
 name = "libredox"
 version = "0.1.3"
@@ -2088,6 +2174,12 @@ dependencies = [
  "unicase",
 ]
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
 [[package]]
 name = "miniz_oxide"
 version = "0.8.2"
@@ -2194,6 +2286,16 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
 
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
 [[package]]
 name = "nu-ansi-term"
 version = "0.46.0"
@@ -2503,7 +2605,7 @@ version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
 dependencies = [
- "ttf-parser",
+ "ttf-parser 0.25.1",
 ]
 
 [[package]]
@@ -2669,6 +2771,12 @@ version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
 
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
 [[package]]
 name = "quick-xml"
 version = "0.26.0"
@@ -2697,6 +2805,16 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "quick-xml"
+version = "0.37.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.38"
@@ -2825,9 +2943,26 @@ dependencies = [
  "log",
  "pico-args",
  "rgb",
- "svgtypes",
+ "svgtypes 0.13.0",
+ "tiny-skia",
+ "usvg 0.37.0",
+]
+
+[[package]]
+name = "resvg"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958"
+dependencies = [
+ "gif",
+ "image-webp",
+ "log",
+ "pico-args",
+ "rgb",
+ "svgtypes 0.15.3",
  "tiny-skia",
- "usvg",
+ "usvg 0.44.0",
+ "zune-jpeg",
 ]
 
 [[package]]
@@ -2859,7 +2994,7 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
 dependencies = [
- "base64",
+ "base64 0.21.7",
  "bitflags 2.7.0",
  "serde",
  "serde_derive",
@@ -2871,6 +3006,12 @@ version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
 
+[[package]]
+name = "roxmltree"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.24"
@@ -2902,6 +3043,24 @@ version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
 
+[[package]]
+name = "rustybuzz"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181"
+dependencies = [
+ "bitflags 2.7.0",
+ "bytemuck",
+ "core_maths",
+ "log",
+ "smallvec",
+ "ttf-parser 0.24.1",
+ "unicode-bidi-mirroring",
+ "unicode-ccc",
+ "unicode-properties",
+ "unicode-script",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.18"
@@ -2958,7 +3117,10 @@ dependencies = [
  "glam",
  "mavlink-bindgen",
  "mint",
+ "nom",
  "parking_lot",
+ "quick-xml 0.37.2",
+ "resvg 0.44.0",
  "ring-channel",
  "serde",
  "serde_json",
@@ -3122,6 +3284,12 @@ version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
 
+[[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
 [[package]]
 name = "skyward_mavlink"
 version = "0.1.0"
@@ -3278,8 +3446,18 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70"
 dependencies = [
- "kurbo",
- "siphasher",
+ "kurbo 0.9.5",
+ "siphasher 0.3.11",
+]
+
+[[package]]
+name = "svgtypes"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
+dependencies = [
+ "kurbo 0.11.1",
+ "siphasher 1.0.1",
 ]
 
 [[package]]
@@ -3433,6 +3611,21 @@ dependencies = [
  "zerovec",
 ]
 
+[[package]]
+name = "tinyvec"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
 [[package]]
 name = "tokio"
 version = "1.43.0"
@@ -3526,6 +3719,15 @@ dependencies = [
  "tracing-log",
 ]
 
+[[package]]
+name = "ttf-parser"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a"
+dependencies = [
+ "core_maths",
+]
+
 [[package]]
 name = "ttf-parser"
 version = "0.25.1"
@@ -3564,18 +3766,54 @@ version = "2.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
 
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
+name = "unicode-bidi-mirroring"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f"
+
+[[package]]
+name = "unicode-ccc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42"
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
 
+[[package]]
+name = "unicode-properties"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+
+[[package]]
+name = "unicode-script"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
+
 [[package]]
 name = "unicode-segmentation"
 version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
 [[package]]
 name = "unicode-width"
 version = "0.1.14"
@@ -3605,7 +3843,7 @@ version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756"
 dependencies = [
- "base64",
+ "base64 0.21.7",
  "log",
  "pico-args",
  "usvg-parser",
@@ -3613,6 +3851,33 @@ dependencies = [
  "xmlwriter",
 ]
 
+[[package]]
+name = "usvg"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6"
+dependencies = [
+ "base64 0.22.1",
+ "data-url",
+ "flate2",
+ "fontdb",
+ "imagesize 0.13.0",
+ "kurbo 0.11.1",
+ "log",
+ "pico-args",
+ "roxmltree 0.20.0",
+ "rustybuzz",
+ "simplecss",
+ "siphasher 1.0.1",
+ "strict-num",
+ "svgtypes 0.15.3",
+ "tiny-skia-path",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "xmlwriter",
+]
+
 [[package]]
 name = "usvg-parser"
 version = "0.37.0"
@@ -3621,13 +3886,13 @@ checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc"
 dependencies = [
  "data-url",
  "flate2",
- "imagesize",
- "kurbo",
+ "imagesize 0.12.0",
+ "kurbo 0.9.5",
  "log",
- "roxmltree",
+ "roxmltree 0.19.0",
  "simplecss",
- "siphasher",
- "svgtypes",
+ "siphasher 0.3.11",
+ "svgtypes 0.13.0",
  "usvg-tree",
 ]
 
@@ -3639,7 +3904,7 @@ checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3"
 dependencies = [
  "rctree",
  "strict-num",
- "svgtypes",
+ "svgtypes 0.13.0",
  "tiny-skia-path",
 ]
 
@@ -3907,6 +4172,12 @@ dependencies = [
  "web-sys",
 ]
 
+[[package]]
+name = "weezl"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
+
 [[package]]
 name = "wgpu"
 version = "22.1.0"
@@ -4672,6 +4943,21 @@ dependencies = [
  "syn 2.0.95",
 ]
 
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
+dependencies = [
+ "zune-core",
+]
+
 [[package]]
 name = "zvariant"
 version = "4.2.0"
diff --git a/Cargo.toml b/Cargo.toml
index e5d25ebd71f6c20bf32f94c14eaf85396cc8f923..2e068e386b9cd705edb66602ed6b1a0c489045a7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,3 +51,6 @@ thiserror = "2.0.7"
 uuid = { version = "1.12.1", features = ["serde", "v7"] }
 glam = { version = "0.29", features = ["serde", "mint"] }
 mint = "0.5.9"
+quick-xml = { version = "0.37", features = ["serialize"] }
+nom = "7.1"
+resvg = "0.44"
diff --git a/src/ui/panes.rs b/src/ui/panes.rs
index d8e52d1e66f1ae3c67700db06202731c1d0e4b06..1cf0a7f077957d11160fda6005a16c1083df2670 100644
--- a/src/ui/panes.rs
+++ b/src/ui/panes.rs
@@ -1,7 +1,8 @@
 mod default;
 mod messages_viewer;
-mod pid;
+pub mod pid;
 mod pid_drawing_tool;
+pub mod pid_new;
 pub mod plot;
 
 use egui_tiles::TileId;
@@ -50,11 +51,14 @@ pub enum PaneKind {
     #[strum(message = "Plot 2D")]
     Plot2D(plot::Plot2DPane),
 
-    #[strum(message = "PID Old")]
-    PidOld(pid_drawing_tool::PidPane),
+    #[strum(message = "PID 1")]
+    PidOld(pid_drawing_tool::Pid1),
 
-    #[strum(message = "PID New")]
-    Pid(pid::Pid),
+    #[strum(message = "PID 2")]
+    Pid(pid::Pid2),
+
+    #[strum(message = "PID 3")]
+    PidNew(pid_new::Pid3),
 }
 
 impl Default for PaneKind {
diff --git a/src/ui/panes/pid.rs b/src/ui/panes/pid.rs
new file mode 100644
index 0000000000000000000000000000000000000000..12753b66bd267e3424f4d2b3efabc639a4a288e3
--- /dev/null
+++ b/src/ui/panes/pid.rs
@@ -0,0 +1,208 @@
+mod grid;
+mod svg;
+
+use super::PaneBehavior;
+use crate::ui::composable_view::PaneResponse;
+use egui::{
+    epaint::ImageDelta, Color32, Context, CursorIcon, PointerButton, Pos2, Rect, Sense,
+    TextureOptions, Theme, Ui,
+};
+use egui_tiles::TileId;
+use grid::Grid;
+use resvg::tiny_skia::IntSize;
+use serde::{Deserialize, Serialize};
+use svg::Svg;
+
+#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone)]
+pub struct Pid2 {
+    svg: Svg,
+    grid: Grid,
+
+    #[serde(skip)]
+    editable: bool,
+
+    center_content: bool,
+
+    cache_valid: bool,
+    texture_id: Option<egui::epaint::TextureId>,
+
+    selected_element: Option<usize>,
+}
+
+impl PaneBehavior for Pid2 {
+    fn ui(&mut self, ui: &mut egui::Ui, _: TileId) -> PaneResponse {
+        let theme = Self::find_theme(ui.ctx());
+
+        self.grid.draw(ui, theme);
+        self.draw_svg(ui);
+
+        let (_, response) = ui.allocate_at_least(ui.max_rect().size(), Sense::click_and_drag());
+        if let Some(pos) = response.hover_pos().map(|p| self.grid.screen_to_grid(p)) {
+            if let Some((idx, elem)) = self
+                .svg
+                .iter_mut_elements()
+                .enumerate()
+                .find(|(_, e)| e.hovered(pos))
+            {
+                // Handle hovering
+                if elem.draggable() {
+                    ui.ctx().output_mut(|o| o.cursor_icon = CursorIcon::Grab);
+                }
+
+                // Handle drag
+                if response.drag_started_by(PointerButton::Primary) {
+                    println!("Drag started on {}", elem.who_am_i());
+                } else if response.drag_stopped_by(PointerButton::Primary) {
+                    println!("Drag stopped on {}", elem.who_am_i());
+                }
+
+                // Handle clicks
+                if response.clicked_by(PointerButton::Secondary) {
+                    self.selected_element = Some(idx);
+                }
+            }
+        }
+
+        // Handle context menu
+        if let Some(idx) = self.selected_element {
+            response.context_menu(|ui| {
+                if ui.button("Delete").clicked() {
+                    println!("We need to delete {idx}");
+                }
+            });
+        }
+
+        PaneResponse::default()
+    }
+
+    fn contains_pointer(&self) -> bool {
+        false
+    }
+}
+
+impl Pid2 {
+    /// Returns the currently used theme
+    fn find_theme(ctx: &Context) -> Theme {
+        // In Egui you can either decide a theme or use the system one.
+        // If the system theme cannot be determined, a fallback theme can be set.
+        ctx.options(|options| match options.theme_preference {
+            egui::ThemePreference::Light => Theme::Light,
+            egui::ThemePreference::Dark => Theme::Dark,
+            egui::ThemePreference::System => match ctx.system_theme() {
+                Some(Theme::Light) => Theme::Light,
+                Some(Theme::Dark) => Theme::Dark,
+                None => options.fallback_theme,
+            },
+        })
+    }
+
+    pub fn from_file() -> Self {
+        let test = String::from_utf8(std::fs::read("test_assets/simple_pid.svg").unwrap()).unwrap();
+        let mut des = quick_xml::de::Deserializer::from_str(&test);
+        Self {
+            svg: Svg::deserialize(&mut des).unwrap(),
+            grid: Grid::from_size(50.0),
+            editable: false,
+            center_content: false,
+            cache_valid: false,
+            texture_id: None,
+            selected_element: None,
+        }
+    }
+
+    fn draw_svg(&mut self, ui: &mut Ui) {
+        let texture_id = match self.texture_id {
+            Some(texture_id) => {
+                if !self.cache_valid {
+                    let image = self.rasterize_svg().unwrap();
+                    ui.ctx().tex_manager().write().set(
+                        texture_id,
+                        ImageDelta::full(image, TextureOptions::default()),
+                    );
+                    self.cache_valid = true;
+                }
+                texture_id
+            }
+            None => {
+                let image = self.rasterize_svg().unwrap();
+                let texture_id = ui.ctx().tex_manager().write().alloc(
+                    "pid".to_string(),
+                    image.into(),
+                    TextureOptions::default(),
+                );
+                println!(
+                    "Texture meta: {:?}",
+                    ui.ctx().tex_manager().read().meta(texture_id)
+                );
+                self.texture_id = Some(texture_id);
+                self.cache_valid = true;
+                texture_id
+            }
+        };
+        // egui::Image::from_texture((
+        //     texture_id,
+        //     egui::Vec2::new(
+        //         self.svg.width * self.grid.size(),
+        //         self.svg.height * self.grid.size(),
+        //     ),
+        // ))
+        // .paint_at(
+        //     ui,
+        //     Rect::from_min_size(
+        //         Pos2::new(0.0, 0.0),
+        //         egui::Vec2::new(
+        //             self.svg.width * self.grid.size(),
+        //             self.svg.height * self.grid.size(),
+        //         ),
+        //     ),
+        // );
+
+        let painter = ui.painter();
+        let rect = Rect::from_min_size(
+            Pos2::new(0.0, 0.0),
+            egui::Vec2::new(
+                self.svg.width * self.grid.size(),
+                self.svg.height * self.grid.size(),
+            ),
+        );
+        painter.image(
+            texture_id,
+            rect,
+            Rect::from_min_max(Pos2::new(0.0, 0.0), Pos2::new(1.0, 1.0)),
+            Color32::WHITE,
+        );
+    }
+
+    fn rasterize_svg(&self) -> Result<egui::ColorImage, String> {
+        let mut serialized = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut serialized, Some("svg")).unwrap();
+        self.svg.serialize(ser).unwrap();
+        let svg_bytes: &[u8] = serialized.as_bytes();
+
+        use resvg::tiny_skia::{Pixmap, Transform};
+        use resvg::usvg::{Options, Tree};
+
+        let mut opt = Options::default();
+        opt.fontdb_mut().load_system_fonts();
+        let rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?;
+        let size = rtree.size().to_int_size();
+        println!("Original svg size: {size:?}");
+
+        let transform = Transform::from_scale(self.grid.size(), self.grid.size());
+        let size = IntSize::from_wh(
+            (transform.sx * size.width() as f32).ceil() as u32,
+            (transform.sy * size.height() as f32).ceil() as u32,
+        )
+        .ok_or_else(|| format!("Failed to compute SVG size"))?;
+        println!("Scaled svg size: {size:?}");
+        let mut pixmap = Pixmap::new(size.width(), size.height())
+            .ok_or_else(|| format!("Failed to create SVG Pixmap of size {size:?}"))?;
+        resvg::render(&rtree, transform, &mut pixmap.as_mut());
+        let image = egui::ColorImage::from_rgba_unmultiplied(
+            [size.width() as _, size.height() as _],
+            pixmap.data(),
+        );
+        println!("Rasterized image: {image:?}");
+        Ok(image)
+    }
+}
diff --git a/src/ui/panes/pid/grid.rs b/src/ui/panes/pid/grid.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fa6ffe28c003b042cdb00d14d719ed29060cdfb3
--- /dev/null
+++ b/src/ui/panes/pid/grid.rs
@@ -0,0 +1,98 @@
+use core::f32;
+
+use egui::{Color32, Pos2, Theme, Ui, Vec2};
+use serde::{Deserialize, Serialize};
+
+const DEFAULT_SIZE: f32 = 10.0;
+const MIN_SIZE: f32 = 5.0;
+const MAX_SIZE: f32 = 50.0;
+const SCROLL_DELTA: f32 = 1.0;
+
+pub const CONNECTION_LINE_THRESHOLD: f32 = 5.0; // Pixels
+pub const CONNECTION_LINE_THICKNESS: f32 = 0.2; // Grid units
+pub const CONNECTION_POINT_SIZE: f32 = 1.0; // Grid units
+
+#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
+pub struct Grid {
+    pub zero_pos: Vec2,
+    size: f32,
+}
+
+impl Default for Grid {
+    fn default() -> Self {
+        Self {
+            zero_pos: Vec2::ZERO,
+            size: DEFAULT_SIZE,
+        }
+    }
+}
+
+impl Grid {
+    pub fn from_size(size: f32) -> Self {
+        Self {
+            zero_pos: Vec2::ZERO,
+            size,
+        }
+    }
+
+    /// Returns the grid size
+    pub fn size(&self) -> f32 {
+        self.size
+    }
+
+    /// Applies the scroll delta at the given position (in screen coordinates)
+    pub fn apply_scroll_delta(&mut self, delta: f32, pos_s: Vec2) {
+        if delta == 0.0 || delta == f32::NAN {
+            return;
+        }
+
+        let old_size = self.size;
+        let delta = delta.signum() * SCROLL_DELTA;
+        self.size = (self.size + delta).clamp(MIN_SIZE, MAX_SIZE);
+
+        if self.size != old_size {
+            self.zero_pos += (delta / old_size) * (self.zero_pos - pos_s);
+        }
+    }
+
+    /// Grid to screen coordinates transformation
+    pub fn grid_to_screen(&self, p_g: Pos2) -> Pos2 {
+        p_g * self.size + self.zero_pos
+    }
+
+    /// Screen to grid coordinates transformation
+    pub fn screen_to_grid(&self, p_s: Pos2) -> Pos2 {
+        (p_s - self.zero_pos) / self.size
+    }
+
+    fn dots_color(theme: Theme) -> Color32 {
+        match theme {
+            Theme::Dark => Color32::DARK_GRAY,
+            Theme::Light => Color32::BLACK,
+        }
+    }
+
+    pub fn draw(&self, ui: &Ui, theme: Theme) {
+        let painter = ui.painter();
+        let window_rect = ui.max_rect();
+        let dot_color = Self::dots_color(theme);
+
+        let offset_x = (self.zero_pos.x % self.size()) as i32;
+        let offset_y = (self.zero_pos.y % self.size()) as i32;
+
+        let start_x = (window_rect.min.x / self.size()) as i32 * self.size() as i32 + offset_x;
+        let end_x = (window_rect.max.x / self.size() + 2.0) as i32 * self.size() as i32 + offset_x;
+        let start_y = (window_rect.min.y / self.size()) as i32 * self.size() as i32 + offset_y;
+        let end_y = (window_rect.max.y / self.size() + 2.0) as i32 * self.size() as i32 + offset_y;
+
+        for x in (start_x..end_x).step_by(self.size() as usize) {
+            for y in (start_y..end_y).step_by(self.size() as usize) {
+                let rect = egui::Rect::from_min_size(
+                    egui::Pos2::new(x as f32, y as f32),
+                    egui::Vec2::new(1.0, 1.0),
+                );
+                painter.rect_filled(rect, 0.0, dot_color);
+            }
+        }
+    }
+}
diff --git a/src/ui/panes/pid/svg.rs b/src/ui/panes/pid/svg.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91ab21fa82d3df2e3b9c1befa1fad81edacfd056
--- /dev/null
+++ b/src/ui/panes/pid/svg.rs
@@ -0,0 +1,226 @@
+pub mod attributes;
+pub mod elements;
+pub mod utils;
+
+use std::slice::{Iter, IterMut};
+
+use elements::{defs::Defs, text::Text, use_node::Use};
+use serde::{Deserialize, Serialize};
+use utils::{is_default, is_zero};
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct Svg {
+    #[serde(rename = "@width")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub width: f32,
+
+    #[serde(rename = "@height")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub height: f32,
+
+    #[serde(rename = "@version")]
+    pub version: f32, // 1.1
+
+    #[serde(rename = "@xmlns")]
+    pub xmlns: String, // http://www.w3.org/2000/svg
+
+    #[serde(rename = "defs")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub defs: Defs,
+
+    #[serde(rename = "use")]
+    #[serde(default)]
+    pub uses: Vec<Use>,
+
+    #[serde(rename = "text")]
+    #[serde(default)]
+    pub texts: Vec<Text>,
+}
+
+impl Default for Svg {
+    fn default() -> Self {
+        Self {
+            width: 0.0,
+            height: 0.0,
+            version: 1.1,
+            xmlns: "http://www.w3.org/2000/svg".to_string(),
+            defs: Defs::default(),
+            uses: Vec::new(),
+            texts: Vec::new(),
+        }
+    }
+}
+
+impl Svg {
+    pub fn iter_elements(&self) -> Iter<Use> {
+        self.uses.iter()
+    }
+
+    pub fn iter_mut_elements(&mut self) -> IterMut<Use> {
+        self.uses.iter_mut()
+    }
+
+    pub fn element_at(&self, idx: usize) -> Option<&Use> {
+        self.uses.get(idx)
+    }
+
+    pub fn remove_element_at(&mut self, idx: usize) {
+        self.uses.remove(idx);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use attributes::d::{
+        close_path::ClosePath, ellicptical_arc::EllipticalArc, horizonta_line_to::HorizontalLineTo,
+        line_to::LineTo, move_to::MoveTo, vertical_line_to::VerticalLineTo, D,
+    };
+    use attributes::style::{LineJoin, Style};
+    use attributes::transform::{Rotate, Transform, Translate};
+    use egui::Color32;
+    use elements::path::Path;
+
+    use super::*;
+
+    fn test_svg() -> Svg {
+        Svg {
+            width: 14.0,
+            height: 17.196152,
+            defs: Defs {
+                paths: vec![
+                    Path {
+                        id: "arrow".to_string(),
+                        width: 4.0,
+                        height: 4.0,
+                        d: D {
+                            segments: vec![
+                                MoveTo::abs(0.7, 2.0),
+                                LineTo::rel(2.6, -1.5),
+                                VerticalLineTo::rel(3.0),
+                                ClosePath::token(),
+                                MoveTo::abs(0.0, 2.0),
+                                HorizontalLineTo::rel(4.0),
+                            ],
+                        },
+                        style: Style {
+                            stroke: Color32::BLACK,
+                            stroke_width: 0.2,
+                            stroke_linejoin: LineJoin::Round,
+                            ..Default::default()
+                        },
+                        ..Default::default()
+                    },
+                    Path {
+                        id: "burst_disk".to_string(),
+                        width: 4.0,
+                        height: 6.0,
+                        d: D {
+                            segments: vec![
+                                MoveTo::abs(0.5, 0.0),
+                                VerticalLineTo::abs(6.0),
+                                MoveTo::abs(1.5, 0.0),
+                                VerticalLineTo::rel(1.0),
+                                EllipticalArc::rel(2.0, 2.0, 0.0, true, true, 0.0, 4.0),
+                                VerticalLineTo::rel(1.0),
+                                MoveTo::abs(0.0, 3.0),
+                                HorizontalLineTo::rel(0.5),
+                                MoveTo::abs(3.5, 3.0),
+                                HorizontalLineTo::rel(0.5),
+                            ],
+                        },
+                        style: Style {
+                            stroke: Color32::BLACK,
+                            fill: Color32::TRANSPARENT,
+                            stroke_width: 0.2,
+                            stroke_linejoin: LineJoin::Round,
+                            ..Default::default()
+                        },
+                        ..Default::default()
+                    },
+                ],
+            },
+            uses: vec![
+                Use {
+                    href: "arrow".to_string(),
+                    width: 4.0,
+                    height: 4.0,
+                    transform: Transform {
+                        rotate: Rotate {
+                            angle: 180.0,
+                            x: 7.0,
+                            y: 8.0,
+                        },
+                        translate: Translate { x: 5.0, y: 6.0 },
+                    },
+                },
+                Use {
+                    href: "burst_disk".to_string(),
+                    width: 4.0,
+                    height: 6.0,
+                    transform: Transform {
+                        translate: Translate { x: 1.0, y: 5.0 },
+                        ..Default::default()
+                    },
+                },
+            ],
+            texts: vec![Text {
+                font_size: 2.0,
+                font_family: "monospace".to_string(),
+                transform: Transform {
+                    translate: Translate { x: 1.0, y: 3.0 },
+                    ..Default::default()
+                },
+                format: "{:.2f}".to_string(),
+                text: "Hi mom!".to_string(),
+            }],
+            ..Default::default()
+        }
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = test_svg();
+        let expected =
+            String::from_utf8(std::fs::read("test_assets/simple_pid.svg").unwrap()).unwrap();
+
+        let mut serialized = String::new();
+        let mut ser = quick_xml::se::Serializer::with_root(&mut serialized, Some("svg")).unwrap();
+        ser.indent(' ', 4);
+        test.serialize(ser).unwrap();
+        assert_eq!(serialized, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Svg::default();
+        let expected = "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"/>";
+
+        let mut serialized = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut serialized, Some("svg")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(serialized, expected);
+    }
+
+    #[test]
+    fn it_deserializes() {
+        let test = String::from_utf8(std::fs::read("test_assets/simple_pid.svg").unwrap()).unwrap();
+        let expected = test_svg();
+
+        let mut des = quick_xml::de::Deserializer::from_str(&test);
+        let deserialized = Svg::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let svg = "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"/>";
+        let expected = Svg::default();
+
+        let mut des = quick_xml::de::Deserializer::from_str(svg);
+        let deserialized = Svg::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes.rs b/src/ui/panes/pid/svg/attributes.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d8c84e457e8353871d3be571e8df6756c36abac2
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes.rs
@@ -0,0 +1,3 @@
+pub mod d;
+pub mod style;
+pub mod transform;
diff --git a/src/ui/panes/pid/svg/attributes/d.rs b/src/ui/panes/pid/svg/attributes/d.rs
new file mode 100644
index 0000000000000000000000000000000000000000..564d5d0c9aef94e0413a3ac70c3ce37f9380b028
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d.rs
@@ -0,0 +1,161 @@
+pub mod close_path;
+pub mod ellicptical_arc;
+pub mod horizonta_line_to;
+pub mod line_to;
+pub mod move_to;
+pub mod vertical_line_to;
+
+use std::fmt::Display;
+
+use self::{
+    close_path::ClosePath, ellicptical_arc::EllipticalArc, horizonta_line_to::HorizontalLineTo,
+    line_to::LineTo, move_to::MoveTo, vertical_line_to::VerticalLineTo,
+};
+use nom::{branch::alt, character::complete::char, multi::separated_list0, IResult};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct D {
+    pub segments: Vec<DToken>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum DToken {
+    MoveTo(MoveTo),
+    LineTo(LineTo),
+    HorizontalLineTo(HorizontalLineTo),
+    VerticalLineTo(VerticalLineTo),
+    EllipticalArc(EllipticalArc),
+    ClosePath(ClosePath),
+}
+
+impl Display for DToken {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::MoveTo(t) => t.fmt(f),
+            Self::LineTo(t) => t.fmt(f),
+            Self::HorizontalLineTo(t) => t.fmt(f),
+            Self::VerticalLineTo(t) => t.fmt(f),
+            Self::EllipticalArc(t) => t.fmt(f),
+            Self::ClosePath(t) => t.fmt(f),
+        }
+    }
+}
+
+impl Serialize for D {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        self.segments
+            .iter()
+            .map(|s| s.to_string())
+            .collect::<Vec<String>>()
+            .join(" ")
+            .serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for D {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let input = String::deserialize(deserializer)?;
+        let d = separated_list0(char(' '), DToken::parse)(input.as_str())
+            .map(|(_, segments)| Self { segments })
+            .unwrap_or_default();
+        Ok(d)
+    }
+}
+
+impl DToken {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        alt((
+            MoveTo::parse,
+            LineTo::parse,
+            HorizontalLineTo::parse,
+            VerticalLineTo::parse,
+            ClosePath::parse,
+            EllipticalArc::parse,
+        ))(input)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Test {
+        #[serde(rename = "@d")]
+        d: D,
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = Test {
+            d: D {
+                segments: vec![
+                    MoveTo::abs(-3.12, 1.0),
+                    LineTo::rel(4.0, -4.0),
+                    MoveTo::rel(-1.0, 1.0),
+                    LineTo::abs(5.0, 0.0),
+                ],
+            },
+        };
+        let expected = "<test d=\"M -3.12 1 l 4 -4 m -1 1 L 5 0\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Test { d: D::default() };
+        let expected = "<test d=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test =
+            "<test d=\"M 0.5 0 V 6 M 1.5 0 v 1 a 2 2 0 1 1 0 4 v 1 M 0 3 h 0.5 M 3.5 3 h 0.5\"/>";
+        let expected = Test {
+            d: D {
+                segments: vec![
+                    MoveTo::abs(0.5, 0.0),
+                    VerticalLineTo::abs(6.0),
+                    MoveTo::abs(1.5, 0.0),
+                    VerticalLineTo::rel(1.0),
+                    EllipticalArc::rel(2.0, 2.0, 0.0, true, true, 0.0, 4.0),
+                    VerticalLineTo::rel(1.0),
+                    MoveTo::abs(0.0, 3.0),
+                    HorizontalLineTo::rel(0.5),
+                    MoveTo::abs(3.5, 3.0),
+                    HorizontalLineTo::rel(0.5),
+                ],
+            },
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<test d=\"\"/>";
+        let expected = Test { d: D::default() };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/d/close_path.rs b/src/ui/panes/pid/svg/attributes/d/close_path.rs
new file mode 100644
index 0000000000000000000000000000000000000000..923ee523c0c516c13dac739c44e36a6f4f4a3936
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d/close_path.rs
@@ -0,0 +1,22 @@
+use super::DToken;
+use nom::{branch::alt, character::complete::char, combinator::map, IResult};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct ClosePath {}
+
+impl ClosePath {
+    pub fn token() -> DToken {
+        DToken::ClosePath(Self {})
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(alt((char('Z'), char('z'))), |_| DToken::ClosePath(Self {}))(input)
+    }
+}
+
+impl Display for ClosePath {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Z")
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/d/ellicptical_arc.rs b/src/ui/panes/pid/svg/attributes/d/ellicptical_arc.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3fd93c2e9f8c549c182e5c995e975a975a16c4bd
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d/ellicptical_arc.rs
@@ -0,0 +1,112 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::anychar,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct EllipticalArc {
+    abs: bool,
+    rx: f32,
+    ry: f32,
+    angle: f32,
+    large_arc: bool,
+    sweep: bool,
+    x: f32,
+    y: f32,
+}
+
+impl EllipticalArc {
+    pub fn abs(
+        rx: f32,
+        ry: f32,
+        angle: f32,
+        large_arc: bool,
+        sweep: bool,
+        x: f32,
+        y: f32,
+    ) -> DToken {
+        DToken::EllipticalArc(Self {
+            abs: true,
+            rx,
+            ry,
+            angle,
+            large_arc,
+            sweep,
+            x,
+            y,
+        })
+    }
+
+    pub fn rel(
+        rx: f32,
+        ry: f32,
+        angle: f32,
+        large_arc: bool,
+        sweep: bool,
+        x: f32,
+        y: f32,
+    ) -> DToken {
+        DToken::EllipticalArc(Self {
+            abs: false,
+            rx,
+            ry,
+            angle,
+            large_arc,
+            sweep,
+            x,
+            y,
+        })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('A'), |_| true), map(char('a'), |_| false))),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+                map(preceded(char(' '), anychar), |c| c == '1'),
+                map(preceded(char(' '), anychar), |c| c == '1'),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+            )),
+            |(abs, rx, ry, angle, large_arc, sweep, x, y)| {
+                DToken::EllipticalArc(Self {
+                    abs,
+                    rx,
+                    ry,
+                    angle,
+                    large_arc,
+                    sweep,
+                    x,
+                    y,
+                })
+            },
+        )(input)
+    }
+}
+
+impl Display for EllipticalArc {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let prefix = if self.abs { 'A' } else { 'a' };
+        write!(
+            f,
+            "{} {} {} {} {} {} {} {}",
+            prefix,
+            self.rx,
+            self.ry,
+            self.angle,
+            self.large_arc as i32,
+            self.sweep as i32,
+            self.x,
+            self.y
+        )
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/d/horizonta_line_to.rs b/src/ui/panes/pid/svg/attributes/d/horizonta_line_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..49d9dbca3ed160bdcbf961cdf7123ac33d754048
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d/horizonta_line_to.rs
@@ -0,0 +1,46 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct HorizontalLineTo {
+    abs: bool,
+    x: f32,
+}
+
+impl HorizontalLineTo {
+    pub fn abs(x: f32) -> DToken {
+        DToken::HorizontalLineTo(Self { abs: true, x })
+    }
+
+    pub fn rel(x: f32) -> DToken {
+        DToken::HorizontalLineTo(Self { abs: false, x })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('H'), |_| true), map(char('h'), |_| false))),
+                preceded(char(' '), float),
+            )),
+            |(abs, x)| DToken::HorizontalLineTo(Self { abs, x }),
+        )(input)
+    }
+}
+
+impl Display for HorizontalLineTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "H {}", self.x)
+        } else {
+            write!(f, "h {}", self.x)
+        }
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/d/line_to.rs b/src/ui/panes/pid/svg/attributes/d/line_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b2c348f1e97b4d772b3d2c12ee350e011efbece4
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d/line_to.rs
@@ -0,0 +1,48 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct LineTo {
+    abs: bool,
+    x: f32,
+    y: f32,
+}
+
+impl LineTo {
+    pub fn abs(x: f32, y: f32) -> DToken {
+        DToken::LineTo(Self { abs: true, x, y })
+    }
+
+    pub fn rel(x: f32, y: f32) -> DToken {
+        DToken::LineTo(Self { abs: false, x, y })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('L'), |_| true), map(char('l'), |_| false))),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+            )),
+            |(abs, x, y)| DToken::LineTo(Self { abs, x, y }),
+        )(input)
+    }
+}
+
+impl Display for LineTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "L {} {}", self.x, self.y)
+        } else {
+            write!(f, "l {} {}", self.x, self.y)
+        }
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/d/move_to.rs b/src/ui/panes/pid/svg/attributes/d/move_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c4d9698a0e40cce9065dc1486de20f0985a4059f
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d/move_to.rs
@@ -0,0 +1,48 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct MoveTo {
+    abs: bool,
+    x: f32,
+    y: f32,
+}
+
+impl MoveTo {
+    pub fn abs(x: f32, y: f32) -> DToken {
+        DToken::MoveTo(Self { abs: true, x, y })
+    }
+
+    pub fn rel(x: f32, y: f32) -> DToken {
+        DToken::MoveTo(Self { abs: false, x, y })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('M'), |_| true), map(char('m'), |_| false))),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+            )),
+            |(abs, x, y)| DToken::MoveTo(Self { abs, x, y }),
+        )(input)
+    }
+}
+
+impl Display for MoveTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "M {} {}", self.x, self.y)
+        } else {
+            write!(f, "m {} {}", self.x, self.y)
+        }
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/d/vertical_line_to.rs b/src/ui/panes/pid/svg/attributes/d/vertical_line_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0284a2bea4104a975abb9236b2b418188ebadb4c
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/d/vertical_line_to.rs
@@ -0,0 +1,46 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct VerticalLineTo {
+    abs: bool,
+    y: f32,
+}
+
+impl VerticalLineTo {
+    pub fn abs(y: f32) -> DToken {
+        DToken::VerticalLineTo(Self { abs: true, y })
+    }
+
+    pub fn rel(y: f32) -> DToken {
+        DToken::VerticalLineTo(Self { abs: false, y })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('V'), |_| true), map(char('v'), |_| false))),
+                preceded(char(' '), float),
+            )),
+            |(abs, y)| DToken::VerticalLineTo(Self { abs, y }),
+        )(input)
+    }
+}
+
+impl Display for VerticalLineTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "V {}", self.y)
+        } else {
+            write!(f, "v {}", self.y)
+        }
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/style.rs b/src/ui/panes/pid/svg/attributes/style.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fc022b95f7392a0704b7b820c3de16e53995aede
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/style.rs
@@ -0,0 +1,212 @@
+use std::fmt::Display;
+
+use egui::Color32;
+use nom::{
+    branch::alt,
+    bytes::complete::tag,
+    bytes::complete::take,
+    character::complete::char,
+    combinator::{map, opt},
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Style {
+    pub fill: Color32,
+    pub stroke: Color32,
+    pub stroke_opacity: f32,
+    pub stroke_width: f32,
+    pub stroke_linejoin: LineJoin,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum LineJoin {
+    Bevel,
+    Miter,
+    Round,
+}
+
+impl Style {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        map(
+            tuple((
+                opt(map(preceded(tag("fill:"), take(9usize)), Color32::from_hex)),
+                opt(char(';')),
+                opt(map(
+                    preceded(tag("stroke:"), take(9usize)),
+                    Color32::from_hex,
+                )),
+                opt(char(';')),
+                opt(preceded(tag("stroke-opacity:"), float)),
+                opt(char(';')),
+                opt(preceded(tag("stroke-width:"), float)),
+                opt(char(';')),
+                opt(preceded(tag("stroke-linejoin:"), LineJoin::parse)),
+            )),
+            |(fill, _, stroke, _, stroke_opacity, _, stroke_width, _, stroke_linejoin)| {
+                let fill = fill.and_then(|c| c.ok()).unwrap_or(Color32::BLACK);
+                let stroke = stroke.and_then(|c| c.ok()).unwrap_or(Color32::TRANSPARENT);
+                Self {
+                    fill,
+                    stroke,
+                    stroke_opacity: stroke_opacity.unwrap_or(1.0),
+                    stroke_width: stroke_width.unwrap_or(1.0),
+                    stroke_linejoin: stroke_linejoin.unwrap_or_default(),
+                }
+            },
+        )(input)
+    }
+}
+
+impl Default for Style {
+    fn default() -> Self {
+        Self {
+            fill: Color32::BLACK,
+            stroke: Color32::TRANSPARENT,
+            stroke_opacity: 1.0,
+            stroke_width: 1.0,
+            stroke_linejoin: LineJoin::Miter,
+        }
+    }
+}
+
+impl Serialize for Style {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut style = Vec::new();
+
+        if self.fill != Color32::BLACK {
+            style.push(format!("fill:{}", self.fill.to_hex()));
+        }
+        if self.stroke != Color32::TRANSPARENT {
+            style.push(format!("stroke:{}", self.stroke.to_hex()));
+        }
+        if self.stroke_opacity != 1.0 {
+            style.push(format!("stroke-opacity:{}", self.stroke_opacity));
+        }
+        if self.stroke_width != 1.0 {
+            style.push(format!("stroke-width:{}", self.stroke_width));
+        }
+        if self.stroke_linejoin != LineJoin::Miter {
+            style.push(format!("stroke-linejoin:{}", self.stroke_linejoin));
+        }
+
+        style.join(";").serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for Style {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let input = String::deserialize(deserializer)?;
+        Ok(Style::parse(input.as_str())
+            .map(|(_, style)| style)
+            .unwrap_or_default())
+    }
+}
+
+impl LineJoin {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        alt((
+            map(tag("bevel"), |_| Self::Bevel),
+            map(tag("miter"), |_| Self::Miter),
+            map(tag("round"), |_| Self::Round),
+        ))(input)
+    }
+}
+
+impl Default for LineJoin {
+    fn default() -> Self {
+        Self::Miter
+    }
+}
+
+impl Display for LineJoin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Bevel => write!(f, "bevel"),
+            Self::Miter => write!(f, "miter"),
+            Self::Round => write!(f, "round"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Test {
+        #[serde(rename = "@style")]
+        style: Style,
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = Test {
+            style: Style {
+                fill: Color32::from_rgb(23, 45, 76),
+                stroke: Color32::from_rgb(123, 42, 29),
+                stroke_opacity: 0.5,
+                stroke_width: 3.21,
+                stroke_linejoin: LineJoin::Bevel,
+            },
+        };
+        let expected = "<test style=\"fill:#172d4cff;stroke:#7b2a1dff;stroke-opacity:0.5;stroke-width:3.21;stroke-linejoin:bevel\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Test {
+            style: Style::default(),
+        };
+        let expected = "<test style=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test  = "<test style=\"fill:#43516fff;stroke:#001831ff;stroke-opacity:0.741;stroke-width:11.5;stroke-linejoin:round\"/>";
+        let expected = Test {
+            style: Style {
+                fill: Color32::from_rgb(67, 81, 111),
+                stroke: Color32::from_rgb(0, 24, 49),
+                stroke_opacity: 0.741,
+                stroke_width: 11.5,
+                stroke_linejoin: LineJoin::Round,
+            },
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<test style=\"\"/>";
+        let expected = Test {
+            style: Style::default(),
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid/svg/attributes/transform.rs b/src/ui/panes/pid/svg/attributes/transform.rs
new file mode 100644
index 0000000000000000000000000000000000000000..821a42bdc80f6032a525494fc8655b9e23babac1
--- /dev/null
+++ b/src/ui/panes/pid/svg/attributes/transform.rs
@@ -0,0 +1,225 @@
+use egui::emath::Rot2;
+use egui::{Pos2, Vec2};
+use nom::IResult;
+use nom::{
+    branch::alt,
+    bytes::complete::tag,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{delimited, preceded, tuple},
+};
+use serde::{Deserialize, Serialize};
+use std::fmt::Display;
+use std::string::ToString;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Transform {
+    pub rotate: Rotate,
+    pub translate: Translate,
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Rotate {
+    pub angle: f32,
+    pub x: f32,
+    pub y: f32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Translate {
+    pub x: f32,
+    pub y: f32,
+}
+
+impl Transform {
+    pub fn to_local_frame(&self, pos: Pos2) -> Pos2 {
+        let pos = pos.to_vec2();
+
+        // Apply the rotation
+        let rot = Rot2::from_angle(-self.rotate.angle);
+        let pos = self.rotate.to_vec2() + rot * (pos - self.rotate.to_vec2());
+
+        // Apply the translation
+        let pos = pos - self.translate.to_vec2();
+
+        pos.to_pos2()
+    }
+}
+
+impl Serialize for Transform {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut transform = String::new();
+        if self.rotate.angle != 0.0 {
+            transform.push_str(&self.rotate.to_string());
+        }
+        if self.translate.x != 0.0 && self.translate.y != 0.0 {
+            transform.push_str(&self.translate.to_string());
+        }
+        transform.serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for Transform {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let input = String::deserialize(deserializer)?;
+
+        let mut transform = Transform::default();
+        let input = if let Ok((input, rotate)) = Rotate::parse(&input) {
+            transform.rotate = rotate;
+            input
+        } else {
+            &input
+        };
+        if let Ok((_, translate)) = Translate::parse(input) {
+            transform.translate = translate;
+        }
+
+        Ok(transform)
+    }
+}
+
+impl Rotate {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        alt((
+            map(
+                delimited(tag("rotate("), preceded(char(' '), float), char(')')),
+                |angle| Self {
+                    angle,
+                    x: 0.0,
+                    y: 0.0,
+                },
+            ),
+            map(
+                delimited(
+                    tag("rotate("),
+                    tuple((
+                        float,
+                        preceded(char(' '), float),
+                        preceded(char(' '), float),
+                    )),
+                    char(')'),
+                ),
+                |(angle, x, y)| Self { angle, x, y },
+            ),
+        ))(input)
+    }
+
+    pub fn to_vec2(&self) -> Vec2 {
+        Vec2::new(self.x, self.y)
+    }
+}
+
+impl Display for Rotate {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.x == 0.0 && self.y == 0.0 {
+            write!(f, "rotate({})", self.angle)
+        } else {
+            write!(f, "rotate({} {} {})", self.angle, self.x, self.y)
+        }
+    }
+}
+
+impl Translate {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        map(
+            delimited(
+                tag("translate("),
+                tuple((float, preceded(char(' '), float))),
+                char(')'),
+            ),
+            |(x, y)| Self { x, y },
+        )(input)
+    }
+
+    pub fn to_vec2(&self) -> Vec2 {
+        Vec2::new(self.x, self.y)
+    }
+}
+
+impl Display for Translate {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "translate({} {})", self.x, self.y)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Test {
+        #[serde(rename = "@transform")]
+        transform: Transform,
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = Test {
+            transform: Transform {
+                rotate: Rotate {
+                    angle: 3.15,
+                    x: 7.0,
+                    y: -1.23,
+                },
+                translate: Translate { x: 4.0, y: -1.4 },
+            },
+        };
+        let expected = "<test transform=\"rotate(3.15 7 -1.23)translate(4 -1.4)\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Test {
+            transform: Transform::default(),
+        };
+        let expected = "<test transform=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test = "<test transform=\"rotate(45.7 -43.21 89)translate(4 -1.4)\"/>";
+        let expected = Test {
+            transform: Transform {
+                rotate: Rotate {
+                    angle: 45.7,
+                    x: -43.21,
+                    y: 89.0,
+                },
+                translate: Translate { x: 4.0, y: -1.4 },
+            },
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<test transform=\"\"/>";
+        let expected = Test {
+            transform: Transform::default(),
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid/svg/elements.rs b/src/ui/panes/pid/svg/elements.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4b42c4d5a06c1809a74ef7641e6fe1ac9b441d25
--- /dev/null
+++ b/src/ui/panes/pid/svg/elements.rs
@@ -0,0 +1,4 @@
+pub mod defs;
+pub mod path;
+pub mod text;
+pub mod use_node;
diff --git a/src/ui/panes/pid/svg/elements/defs.rs b/src/ui/panes/pid/svg/elements/defs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..223a3c9830bf4ea0495273baf97f62eae12bfc0c
--- /dev/null
+++ b/src/ui/panes/pid/svg/elements/defs.rs
@@ -0,0 +1,10 @@
+use super::path::Path;
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, PartialEq, Default, Clone)]
+pub struct Defs {
+    #[serde(rename = "path")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub paths: Vec<Path>,
+}
diff --git a/src/ui/panes/pid/svg/elements/path.rs b/src/ui/panes/pid/svg/elements/path.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a2769f2c96d312ec5308fbe4724c31bba39d28b2
--- /dev/null
+++ b/src/ui/panes/pid/svg/elements/path.rs
@@ -0,0 +1,119 @@
+use crate::ui::panes::pid::svg::{
+    attributes::{
+        d::{DToken, D},
+        style::Style,
+        transform::Transform,
+    },
+    utils::{is_default, is_zero},
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
+pub struct Path {
+    #[serde(rename = "@id")]
+    pub id: String,
+
+    #[serde(rename = "@width")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub width: f32,
+
+    #[serde(rename = "@height")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub height: f32,
+
+    #[serde(rename = "@d")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub d: D,
+
+    #[serde(rename = "@style")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub style: Style,
+
+    #[serde(rename = "@transfrom")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub transform: Transform,
+}
+
+impl Path {
+    pub fn push_segment(&mut self, segment: DToken) {
+        self.d.segments.push(segment);
+        // self.update_size();
+    }
+
+    // fn update_size(&mut self) {
+    //     let size = self
+    //         .d
+    //         .segments
+    //         .iter()
+    //         .map(|s| s.to_vec2())
+    //         .fold(Vec2::new(self.width, self.height), Vec2::max);
+    //     self.width = size.x + self.style.stroke_width / 2.0;
+    //     self.height = size.y + self.style.stroke_width / 2.0;
+    // }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn it_serialize() {
+        let test = Path {
+            id: "paolino".to_string(),
+            width: 23.4,
+            height: 5.0,
+            d: D::default(),
+            style: Style::default(),
+            transform: Transform::default(),
+        };
+        let expected = "<path id=\"paolino\" width=\"23.4\" height=\"5\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("path")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Path::default();
+        let expected = "<path id=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("path")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test = "<test id=\"pepperoncino\" width=\"11\" height=\"24.5\"/>";
+        let expected = Path {
+            id: "pepperoncino".to_string(),
+            width: 11.0,
+            height: 24.5,
+            d: D::default(),
+            style: Style::default(),
+            transform: Transform::default(),
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Path::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<path id=\"\"/>";
+        let expected = Path::default();
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Path::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid/svg/elements/text.rs b/src/ui/panes/pid/svg/elements/text.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8d547ba64d79e92e6d312d2af27a3d98ef6cd902
--- /dev/null
+++ b/src/ui/panes/pid/svg/elements/text.rs
@@ -0,0 +1,43 @@
+use crate::ui::panes::pid::svg::{
+    attributes::transform::Transform,
+    utils::{is_default, is_zero},
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+pub struct Text {
+    #[serde(rename = "@font-size")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub font_size: f32,
+
+    #[serde(rename = "@font-family")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub font_family: String,
+
+    #[serde(rename = "@transform")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub transform: Transform,
+
+    #[serde(rename = "@segs-format")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub format: String,
+
+    #[serde(rename = "$text")]
+    pub text: String,
+}
+
+impl Text {
+    pub fn new(text: String, size: f32) -> Self {
+        Self {
+            font_size: size,
+            font_family: "monospace".to_string(),
+            transform: Transform::default(),
+            format: "TODO".to_string(),
+            text,
+        }
+    }
+}
diff --git a/src/ui/panes/pid/svg/elements/use_node.rs b/src/ui/panes/pid/svg/elements/use_node.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ad3559076462f18b2fefb5d17b841a09660edc58
--- /dev/null
+++ b/src/ui/panes/pid/svg/elements/use_node.rs
@@ -0,0 +1,92 @@
+use crate::ui::panes::pid::svg::{
+    attributes::transform::Transform,
+    utils::{is_default, is_zero},
+};
+use egui::{InputState, Pos2};
+use glam::Vec2;
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+pub struct Use {
+    #[serde(rename = "@href", with = "href")]
+    pub href: String,
+
+    #[serde(rename = "@width")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub width: f32,
+
+    #[serde(rename = "@height")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub height: f32,
+
+    #[serde(rename = "@transform")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub transform: Transform,
+}
+
+impl Use {
+    pub fn handle_click(&mut self, _pos: Pos2) {}
+    pub fn handle_double_click(&mut self, _pos: Pos2) {}
+
+    /// Consumes any shortcut the element should respond to
+    pub fn handle_shortcuts(&mut self, _input: &mut InputState) {}
+
+    pub fn hovered(&self, pos: Pos2) -> bool {
+        let pos = self.transform.to_local_frame(pos).to_vec2();
+
+        // The bounding box in the elemen's frame is defined by the size. But
+        // width and height can be negative. This allows to represent where the
+        // position anchor point is (for paths is the top-left, for texts
+        // is the bottom-left)
+        let size = Vec2::new(self.width, self.height);
+        let min = Vec2::ZERO.min(size);
+        let max = Vec2::ZERO.max(size);
+
+        // Check if the point is in the bounding box
+        min.x <= pos.x && pos.x <= max.x && min.y <= pos.y && pos.y <= max.y
+    }
+
+    /// Whether the elemen can be moved
+    pub fn draggable(&self) -> bool {
+        false
+    }
+
+    /// Whether a window to edit the element's configuration is available
+    pub fn editable(&self) -> bool {
+        false
+    }
+
+    pub fn who_am_i(&self) -> String {
+        format!(
+            "{} at x={} y={} width={} height={}",
+            self.href,
+            self.transform.translate.x,
+            self.transform.translate.y,
+            self.width,
+            self.height
+        )
+    }
+}
+
+mod href {
+    use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+    pub fn serialize<S>(id: &str, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        format!("#{id}").serialize(serializer)
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let mut id = String::deserialize(deserializer)?;
+        id.remove(0);
+        Ok(id)
+    }
+}
diff --git a/src/ui/panes/pid/svg/utils.rs b/src/ui/panes/pid/svg/utils.rs
new file mode 100644
index 0000000000000000000000000000000000000000..16be55525989331c0a3a2c05c4ee320d927020a0
--- /dev/null
+++ b/src/ui/panes/pid/svg/utils.rs
@@ -0,0 +1,7 @@
+pub fn is_default<T: Default + PartialEq>(x: &T) -> bool {
+    *x == T::default()
+}
+
+pub fn is_zero(x: &f32) -> bool {
+    *x == 0.0
+}
diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs
index 35a766e5d90c57ee8de6f4e1de77a9c1a02990ff..d9567fc3aad59ce3b9f2afd1fd64306132c1ebac 100644
--- a/src/ui/panes/pid_drawing_tool.rs
+++ b/src/ui/panes/pid_drawing_tool.rs
@@ -30,7 +30,7 @@ enum Action {
 
 /// Piping and instrumentation diagram
 #[derive(Clone, Serialize, Deserialize, Default, Debug)]
-pub struct PidPane {
+pub struct Pid1 {
     elements: Vec<Element>,
     connections: Vec<Connection>,
 
@@ -45,7 +45,7 @@ pub struct PidPane {
     center_content: bool,
 }
 
-impl PartialEq for PidPane {
+impl PartialEq for Pid1 {
     fn eq(&self, other: &Self) -> bool {
         self.elements == other.elements
             && self.connections == other.connections
@@ -54,9 +54,9 @@ impl PartialEq for PidPane {
     }
 }
 
-impl PaneBehavior for PidPane {
+impl PaneBehavior for Pid1 {
     fn ui(&mut self, ui: &mut egui::Ui, _: TileId) -> PaneResponse {
-        let theme = PidPane::find_theme(ui.ctx());
+        let theme = Pid1::find_theme(ui.ctx());
 
         if self.center_content && !self.editable {
             self.center(ui);
@@ -69,7 +69,9 @@ impl PaneBehavior for PidPane {
         self.draw_elements(ui, theme);
 
         // Handle things that require knowing the position of the pointer
-        let (_, response) = ui.allocate_at_least(ui.max_rect().size(), Sense::click_and_drag());
+        let (rect, response) = ui.allocate_at_least(ui.max_rect().size(), Sense::click_and_drag());
+        println!("Allocated rectangle: {rect:?}");
+        println!("Response: {response:?}");
         if let Some(pointer_pos) = response.hover_pos().map(|p| egui_to_glam(p.to_vec2())) {
             if self.editable {
                 self.handle_zoom(ui, theme, pointer_pos);
@@ -101,7 +103,7 @@ impl PaneBehavior for PidPane {
     }
 }
 
-impl PidPane {
+impl Pid1 {
     /// Returns the currently used theme
     fn find_theme(ctx: &Context) -> Theme {
         // In Egui you can either decide a theme or use the system one.
@@ -155,7 +157,7 @@ impl PidPane {
     fn draw_grid(&self, ui: &Ui, theme: Theme) {
         let painter = ui.painter();
         let window_rect = ui.max_rect();
-        let dot_color = PidPane::dots_color(theme);
+        let dot_color = Pid1::dots_color(theme);
 
         let offset_x = (self.grid.zero_pos.x % self.grid.size()) as i32;
         let offset_y = (self.grid.zero_pos.y % self.grid.size()) as i32;
diff --git a/src/ui/panes/pid_drawing_tool/connections.rs b/src/ui/panes/pid_drawing_tool/connections.rs
index 2a5020f28c79df7b6f4dc09a67118e84fb3553e5..8e8a73324869b92854d04d6da962e5cb0c2d43f5 100644
--- a/src/ui/panes/pid_drawing_tool/connections.rs
+++ b/src/ui/panes/pid_drawing_tool/connections.rs
@@ -6,7 +6,7 @@ use crate::ui::utils::glam_to_egui;
 
 use super::{
     grid::{GridInfo, CONNECTION_LINE_THICKNESS, CONNECTION_LINE_THRESHOLD, CONNECTION_POINT_SIZE},
-    PidPane,
+    Pid1,
 };
 
 #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
@@ -40,7 +40,7 @@ impl Connection {
     }
 
     /// Return the index of the segment the point is on, if any
-    pub fn contains(&self, pid: &PidPane, p_s: Vec2) -> Option<usize> {
+    pub fn contains(&self, pid: &Pid1, p_s: Vec2) -> Option<usize> {
         let p_g = pid.grid.screen_to_grid(p_s);
         let mut points = Vec::new();
 
@@ -94,7 +94,7 @@ impl Connection {
         }
     }
 
-    pub fn draw(&self, pid: &PidPane, painter: &Painter, theme: Theme) {
+    pub fn draw(&self, pid: &Pid1, painter: &Painter, theme: Theme) {
         let color = Connection::line_color(theme);
 
         let start = pid.elements[self.start].anchor_point(self.start_anchor);
diff --git a/src/ui/panes/pid_new/icon.rs b/src/ui/panes/pid_new/icon.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/ui/panes/pid_new/mod.rs b/src/ui/panes/pid_new/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..80d4001409fa7a466f650430dbfa5bb81d309327
--- /dev/null
+++ b/src/ui/panes/pid_new/mod.rs
@@ -0,0 +1,119 @@
+mod icon;
+mod svg;
+
+use super::{PaneBehavior, PaneResponse};
+use anyhow::{Context, Result};
+use egui::{Pos2, Ui};
+use egui_tiles::TileId;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::string::String;
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+struct Grid {
+    pos: Pos2,
+    scale: f32,
+}
+
+impl Default for Grid {
+    fn default() -> Self {
+        Self {
+            pos: Pos2::ZERO,
+            scale: 10.0,
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+enum Element {
+    Icon,
+    Label,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+struct ElementRef {
+    id: String,
+    pos: Pos2,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
+pub struct Pid3 {
+    /// Scale and position of where to draw the diagram on screen
+    grid: Grid,
+
+    /// Elements that can be placed in the diagram
+    elements: HashMap<String, Element>,
+
+    /// Instances of elements
+    references: Vec<ElementRef>,
+}
+
+impl PaneBehavior for Pid3 {
+    fn ui(&mut self, ui: &mut Ui, _: TileId) -> PaneResponse {
+        self.draw_diagram(ui);
+        PaneResponse::default()
+    }
+
+    fn contains_pointer(&self) -> bool {
+        false
+    }
+}
+
+impl Pid3 {
+    fn draw_diagram(&self, ui: &mut Ui) {
+        let image = self.rasterize_svg().unwrap();
+        let texture_id = ui.ctx().tex_manager().write().alloc(
+            "pid".to_string(),
+            image.into(),
+            egui::TextureOptions::default(),
+        );
+        let rect = egui::Rect::from_min_size(
+            Pos2::new(0.0, 0.0),
+            egui::Vec2::new(10.0 * self.grid.scale, 10.0 * self.grid.scale),
+        );
+        ui.painter().image(
+            texture_id,
+            rect,
+            egui::Rect::from_min_max(Pos2::new(0.0, 0.0), Pos2::new(1.0, 1.0)),
+            egui::Color32::WHITE,
+        );
+    }
+
+    fn rasterize_svg(&self) -> Result<egui::ColorImage> {
+        // resvg uses the library roxmltree to represent internally the xml. The
+        // problem is that roxmltree do not allow to build the document, only to
+        // parse a text/file. For this reason we have to first serialize the svg
+        // and then parse it to do the rasterization
+
+        // Serialization
+        let svg = svg::Svg::from(self);
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("svg"))?;
+        svg.serialize(ser)?;
+
+        // Parsing with usvg
+        let mut options = resvg::usvg::Options::default();
+        options.fontdb_mut().load_system_fonts(); // TODO: Do it once
+        let rtree = resvg::usvg::Tree::from_str(buffer.as_str(), &options)?;
+        let size = rtree.size().to_int_size();
+
+        // Configure the scaling with the grid setting
+        let transform = resvg::tiny_skia::Transform::from_scale(self.grid.scale, self.grid.scale);
+        let size = resvg::tiny_skia::IntSize::from_wh(
+            (transform.sx * size.width() as f32).ceil() as u32,
+            (transform.sy * size.height() as f32).ceil() as u32,
+        )
+        .context("Failed to compute SVG size")?;
+
+        // Rasterize
+        let mut pixmap = resvg::tiny_skia::Pixmap::new(size.width(), size.height())
+            .context("Failed to create SVG Pixmap of size {size:?}")?;
+        resvg::render(&rtree, transform, &mut pixmap.as_mut());
+        Ok(egui::ColorImage::from_rgba_unmultiplied(
+            [size.width() as _, size.height() as _],
+            pixmap.data(),
+        ))
+    }
+
+    fn load_default_elements(&mut self) {}
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/close_path.rs b/src/ui/panes/pid_new/svg/attributes/data/close_path.rs
new file mode 100644
index 0000000000000000000000000000000000000000..923ee523c0c516c13dac739c44e36a6f4f4a3936
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/close_path.rs
@@ -0,0 +1,22 @@
+use super::DToken;
+use nom::{branch::alt, character::complete::char, combinator::map, IResult};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct ClosePath {}
+
+impl ClosePath {
+    pub fn token() -> DToken {
+        DToken::ClosePath(Self {})
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(alt((char('Z'), char('z'))), |_| DToken::ClosePath(Self {}))(input)
+    }
+}
+
+impl Display for ClosePath {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Z")
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/ellicptical_arc.rs b/src/ui/panes/pid_new/svg/attributes/data/ellicptical_arc.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3fd93c2e9f8c549c182e5c995e975a975a16c4bd
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/ellicptical_arc.rs
@@ -0,0 +1,112 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::anychar,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct EllipticalArc {
+    abs: bool,
+    rx: f32,
+    ry: f32,
+    angle: f32,
+    large_arc: bool,
+    sweep: bool,
+    x: f32,
+    y: f32,
+}
+
+impl EllipticalArc {
+    pub fn abs(
+        rx: f32,
+        ry: f32,
+        angle: f32,
+        large_arc: bool,
+        sweep: bool,
+        x: f32,
+        y: f32,
+    ) -> DToken {
+        DToken::EllipticalArc(Self {
+            abs: true,
+            rx,
+            ry,
+            angle,
+            large_arc,
+            sweep,
+            x,
+            y,
+        })
+    }
+
+    pub fn rel(
+        rx: f32,
+        ry: f32,
+        angle: f32,
+        large_arc: bool,
+        sweep: bool,
+        x: f32,
+        y: f32,
+    ) -> DToken {
+        DToken::EllipticalArc(Self {
+            abs: false,
+            rx,
+            ry,
+            angle,
+            large_arc,
+            sweep,
+            x,
+            y,
+        })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('A'), |_| true), map(char('a'), |_| false))),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+                map(preceded(char(' '), anychar), |c| c == '1'),
+                map(preceded(char(' '), anychar), |c| c == '1'),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+            )),
+            |(abs, rx, ry, angle, large_arc, sweep, x, y)| {
+                DToken::EllipticalArc(Self {
+                    abs,
+                    rx,
+                    ry,
+                    angle,
+                    large_arc,
+                    sweep,
+                    x,
+                    y,
+                })
+            },
+        )(input)
+    }
+}
+
+impl Display for EllipticalArc {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let prefix = if self.abs { 'A' } else { 'a' };
+        write!(
+            f,
+            "{} {} {} {} {} {} {} {}",
+            prefix,
+            self.rx,
+            self.ry,
+            self.angle,
+            self.large_arc as i32,
+            self.sweep as i32,
+            self.x,
+            self.y
+        )
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/horizonta_line_to.rs b/src/ui/panes/pid_new/svg/attributes/data/horizonta_line_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..49d9dbca3ed160bdcbf961cdf7123ac33d754048
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/horizonta_line_to.rs
@@ -0,0 +1,46 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct HorizontalLineTo {
+    abs: bool,
+    x: f32,
+}
+
+impl HorizontalLineTo {
+    pub fn abs(x: f32) -> DToken {
+        DToken::HorizontalLineTo(Self { abs: true, x })
+    }
+
+    pub fn rel(x: f32) -> DToken {
+        DToken::HorizontalLineTo(Self { abs: false, x })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('H'), |_| true), map(char('h'), |_| false))),
+                preceded(char(' '), float),
+            )),
+            |(abs, x)| DToken::HorizontalLineTo(Self { abs, x }),
+        )(input)
+    }
+}
+
+impl Display for HorizontalLineTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "H {}", self.x)
+        } else {
+            write!(f, "h {}", self.x)
+        }
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/line_to.rs b/src/ui/panes/pid_new/svg/attributes/data/line_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b2c348f1e97b4d772b3d2c12ee350e011efbece4
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/line_to.rs
@@ -0,0 +1,48 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct LineTo {
+    abs: bool,
+    x: f32,
+    y: f32,
+}
+
+impl LineTo {
+    pub fn abs(x: f32, y: f32) -> DToken {
+        DToken::LineTo(Self { abs: true, x, y })
+    }
+
+    pub fn rel(x: f32, y: f32) -> DToken {
+        DToken::LineTo(Self { abs: false, x, y })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('L'), |_| true), map(char('l'), |_| false))),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+            )),
+            |(abs, x, y)| DToken::LineTo(Self { abs, x, y }),
+        )(input)
+    }
+}
+
+impl Display for LineTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "L {} {}", self.x, self.y)
+        } else {
+            write!(f, "l {} {}", self.x, self.y)
+        }
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/mod.rs b/src/ui/panes/pid_new/svg/attributes/data/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f42da87a153d0a2802b71d14a25d613b374bc5e2
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/mod.rs
@@ -0,0 +1,161 @@
+pub mod close_path;
+pub mod ellicptical_arc;
+pub mod horizonta_line_to;
+pub mod line_to;
+pub mod move_to;
+pub mod vertical_line_to;
+
+use std::fmt::Display;
+
+use self::{
+    close_path::ClosePath, ellicptical_arc::EllipticalArc, horizonta_line_to::HorizontalLineTo,
+    line_to::LineTo, move_to::MoveTo, vertical_line_to::VerticalLineTo,
+};
+use nom::{branch::alt, character::complete::char, multi::separated_list0, IResult};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Data {
+    pub segments: Vec<DToken>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum DToken {
+    MoveTo(MoveTo),
+    LineTo(LineTo),
+    HorizontalLineTo(HorizontalLineTo),
+    VerticalLineTo(VerticalLineTo),
+    EllipticalArc(EllipticalArc),
+    ClosePath(ClosePath),
+}
+
+impl Display for DToken {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::MoveTo(t) => t.fmt(f),
+            Self::LineTo(t) => t.fmt(f),
+            Self::HorizontalLineTo(t) => t.fmt(f),
+            Self::VerticalLineTo(t) => t.fmt(f),
+            Self::EllipticalArc(t) => t.fmt(f),
+            Self::ClosePath(t) => t.fmt(f),
+        }
+    }
+}
+
+impl Serialize for Data {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        self.segments
+            .iter()
+            .map(|s| s.to_string())
+            .collect::<Vec<String>>()
+            .join(" ")
+            .serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for Data {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let input = String::deserialize(deserializer)?;
+        let d = separated_list0(char(' '), DToken::parse)(input.as_str())
+            .map(|(_, segments)| Self { segments })
+            .unwrap_or_default();
+        Ok(d)
+    }
+}
+
+impl DToken {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        alt((
+            MoveTo::parse,
+            LineTo::parse,
+            HorizontalLineTo::parse,
+            VerticalLineTo::parse,
+            ClosePath::parse,
+            EllipticalArc::parse,
+        ))(input)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Test {
+        #[serde(rename = "@d")]
+        d: Data,
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = Test {
+            d: Data {
+                segments: vec![
+                    MoveTo::abs(-3.12, 1.0),
+                    LineTo::rel(4.0, -4.0),
+                    MoveTo::rel(-1.0, 1.0),
+                    LineTo::abs(5.0, 0.0),
+                ],
+            },
+        };
+        let expected = "<test d=\"M -3.12 1 l 4 -4 m -1 1 L 5 0\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Test { d: Data::default() };
+        let expected = "<test d=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test =
+            "<test d=\"M 0.5 0 V 6 M 1.5 0 v 1 a 2 2 0 1 1 0 4 v 1 M 0 3 h 0.5 M 3.5 3 h 0.5\"/>";
+        let expected = Test {
+            d: Data {
+                segments: vec![
+                    MoveTo::abs(0.5, 0.0),
+                    VerticalLineTo::abs(6.0),
+                    MoveTo::abs(1.5, 0.0),
+                    VerticalLineTo::rel(1.0),
+                    EllipticalArc::rel(2.0, 2.0, 0.0, true, true, 0.0, 4.0),
+                    VerticalLineTo::rel(1.0),
+                    MoveTo::abs(0.0, 3.0),
+                    HorizontalLineTo::rel(0.5),
+                    MoveTo::abs(3.5, 3.0),
+                    HorizontalLineTo::rel(0.5),
+                ],
+            },
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<test d=\"\"/>";
+        let expected = Test { d: Data::default() };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/move_to.rs b/src/ui/panes/pid_new/svg/attributes/data/move_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c4d9698a0e40cce9065dc1486de20f0985a4059f
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/move_to.rs
@@ -0,0 +1,48 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct MoveTo {
+    abs: bool,
+    x: f32,
+    y: f32,
+}
+
+impl MoveTo {
+    pub fn abs(x: f32, y: f32) -> DToken {
+        DToken::MoveTo(Self { abs: true, x, y })
+    }
+
+    pub fn rel(x: f32, y: f32) -> DToken {
+        DToken::MoveTo(Self { abs: false, x, y })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('M'), |_| true), map(char('m'), |_| false))),
+                preceded(char(' '), float),
+                preceded(char(' '), float),
+            )),
+            |(abs, x, y)| DToken::MoveTo(Self { abs, x, y }),
+        )(input)
+    }
+}
+
+impl Display for MoveTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "M {} {}", self.x, self.y)
+        } else {
+            write!(f, "m {} {}", self.x, self.y)
+        }
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/data/vertical_line_to.rs b/src/ui/panes/pid_new/svg/attributes/data/vertical_line_to.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0284a2bea4104a975abb9236b2b418188ebadb4c
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/data/vertical_line_to.rs
@@ -0,0 +1,46 @@
+use super::DToken;
+use nom::{
+    branch::alt,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct VerticalLineTo {
+    abs: bool,
+    y: f32,
+}
+
+impl VerticalLineTo {
+    pub fn abs(y: f32) -> DToken {
+        DToken::VerticalLineTo(Self { abs: true, y })
+    }
+
+    pub fn rel(y: f32) -> DToken {
+        DToken::VerticalLineTo(Self { abs: false, y })
+    }
+
+    pub(super) fn parse(input: &str) -> IResult<&str, DToken> {
+        map(
+            tuple((
+                alt((map(char('V'), |_| true), map(char('v'), |_| false))),
+                preceded(char(' '), float),
+            )),
+            |(abs, y)| DToken::VerticalLineTo(Self { abs, y }),
+        )(input)
+    }
+}
+
+impl Display for VerticalLineTo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.abs {
+            write!(f, "V {}", self.y)
+        } else {
+            write!(f, "v {}", self.y)
+        }
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/mod.rs b/src/ui/panes/pid_new/svg/attributes/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cda81f58f16faa19c8561b8eb53820735e98017f
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/mod.rs
@@ -0,0 +1,3 @@
+pub mod data;
+pub mod style;
+pub mod transform;
diff --git a/src/ui/panes/pid_new/svg/attributes/style.rs b/src/ui/panes/pid_new/svg/attributes/style.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fc022b95f7392a0704b7b820c3de16e53995aede
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/style.rs
@@ -0,0 +1,212 @@
+use std::fmt::Display;
+
+use egui::Color32;
+use nom::{
+    branch::alt,
+    bytes::complete::tag,
+    bytes::complete::take,
+    character::complete::char,
+    combinator::{map, opt},
+    number::complete::float,
+    sequence::{preceded, tuple},
+    IResult,
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Style {
+    pub fill: Color32,
+    pub stroke: Color32,
+    pub stroke_opacity: f32,
+    pub stroke_width: f32,
+    pub stroke_linejoin: LineJoin,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum LineJoin {
+    Bevel,
+    Miter,
+    Round,
+}
+
+impl Style {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        map(
+            tuple((
+                opt(map(preceded(tag("fill:"), take(9usize)), Color32::from_hex)),
+                opt(char(';')),
+                opt(map(
+                    preceded(tag("stroke:"), take(9usize)),
+                    Color32::from_hex,
+                )),
+                opt(char(';')),
+                opt(preceded(tag("stroke-opacity:"), float)),
+                opt(char(';')),
+                opt(preceded(tag("stroke-width:"), float)),
+                opt(char(';')),
+                opt(preceded(tag("stroke-linejoin:"), LineJoin::parse)),
+            )),
+            |(fill, _, stroke, _, stroke_opacity, _, stroke_width, _, stroke_linejoin)| {
+                let fill = fill.and_then(|c| c.ok()).unwrap_or(Color32::BLACK);
+                let stroke = stroke.and_then(|c| c.ok()).unwrap_or(Color32::TRANSPARENT);
+                Self {
+                    fill,
+                    stroke,
+                    stroke_opacity: stroke_opacity.unwrap_or(1.0),
+                    stroke_width: stroke_width.unwrap_or(1.0),
+                    stroke_linejoin: stroke_linejoin.unwrap_or_default(),
+                }
+            },
+        )(input)
+    }
+}
+
+impl Default for Style {
+    fn default() -> Self {
+        Self {
+            fill: Color32::BLACK,
+            stroke: Color32::TRANSPARENT,
+            stroke_opacity: 1.0,
+            stroke_width: 1.0,
+            stroke_linejoin: LineJoin::Miter,
+        }
+    }
+}
+
+impl Serialize for Style {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut style = Vec::new();
+
+        if self.fill != Color32::BLACK {
+            style.push(format!("fill:{}", self.fill.to_hex()));
+        }
+        if self.stroke != Color32::TRANSPARENT {
+            style.push(format!("stroke:{}", self.stroke.to_hex()));
+        }
+        if self.stroke_opacity != 1.0 {
+            style.push(format!("stroke-opacity:{}", self.stroke_opacity));
+        }
+        if self.stroke_width != 1.0 {
+            style.push(format!("stroke-width:{}", self.stroke_width));
+        }
+        if self.stroke_linejoin != LineJoin::Miter {
+            style.push(format!("stroke-linejoin:{}", self.stroke_linejoin));
+        }
+
+        style.join(";").serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for Style {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let input = String::deserialize(deserializer)?;
+        Ok(Style::parse(input.as_str())
+            .map(|(_, style)| style)
+            .unwrap_or_default())
+    }
+}
+
+impl LineJoin {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        alt((
+            map(tag("bevel"), |_| Self::Bevel),
+            map(tag("miter"), |_| Self::Miter),
+            map(tag("round"), |_| Self::Round),
+        ))(input)
+    }
+}
+
+impl Default for LineJoin {
+    fn default() -> Self {
+        Self::Miter
+    }
+}
+
+impl Display for LineJoin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Bevel => write!(f, "bevel"),
+            Self::Miter => write!(f, "miter"),
+            Self::Round => write!(f, "round"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Test {
+        #[serde(rename = "@style")]
+        style: Style,
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = Test {
+            style: Style {
+                fill: Color32::from_rgb(23, 45, 76),
+                stroke: Color32::from_rgb(123, 42, 29),
+                stroke_opacity: 0.5,
+                stroke_width: 3.21,
+                stroke_linejoin: LineJoin::Bevel,
+            },
+        };
+        let expected = "<test style=\"fill:#172d4cff;stroke:#7b2a1dff;stroke-opacity:0.5;stroke-width:3.21;stroke-linejoin:bevel\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Test {
+            style: Style::default(),
+        };
+        let expected = "<test style=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test  = "<test style=\"fill:#43516fff;stroke:#001831ff;stroke-opacity:0.741;stroke-width:11.5;stroke-linejoin:round\"/>";
+        let expected = Test {
+            style: Style {
+                fill: Color32::from_rgb(67, 81, 111),
+                stroke: Color32::from_rgb(0, 24, 49),
+                stroke_opacity: 0.741,
+                stroke_width: 11.5,
+                stroke_linejoin: LineJoin::Round,
+            },
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<test style=\"\"/>";
+        let expected = Test {
+            style: Style::default(),
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/attributes/transform.rs b/src/ui/panes/pid_new/svg/attributes/transform.rs
new file mode 100644
index 0000000000000000000000000000000000000000..821a42bdc80f6032a525494fc8655b9e23babac1
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/attributes/transform.rs
@@ -0,0 +1,225 @@
+use egui::emath::Rot2;
+use egui::{Pos2, Vec2};
+use nom::IResult;
+use nom::{
+    branch::alt,
+    bytes::complete::tag,
+    character::complete::char,
+    combinator::map,
+    number::complete::float,
+    sequence::{delimited, preceded, tuple},
+};
+use serde::{Deserialize, Serialize};
+use std::fmt::Display;
+use std::string::ToString;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Transform {
+    pub rotate: Rotate,
+    pub translate: Translate,
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Rotate {
+    pub angle: f32,
+    pub x: f32,
+    pub y: f32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Translate {
+    pub x: f32,
+    pub y: f32,
+}
+
+impl Transform {
+    pub fn to_local_frame(&self, pos: Pos2) -> Pos2 {
+        let pos = pos.to_vec2();
+
+        // Apply the rotation
+        let rot = Rot2::from_angle(-self.rotate.angle);
+        let pos = self.rotate.to_vec2() + rot * (pos - self.rotate.to_vec2());
+
+        // Apply the translation
+        let pos = pos - self.translate.to_vec2();
+
+        pos.to_pos2()
+    }
+}
+
+impl Serialize for Transform {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut transform = String::new();
+        if self.rotate.angle != 0.0 {
+            transform.push_str(&self.rotate.to_string());
+        }
+        if self.translate.x != 0.0 && self.translate.y != 0.0 {
+            transform.push_str(&self.translate.to_string());
+        }
+        transform.serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for Transform {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let input = String::deserialize(deserializer)?;
+
+        let mut transform = Transform::default();
+        let input = if let Ok((input, rotate)) = Rotate::parse(&input) {
+            transform.rotate = rotate;
+            input
+        } else {
+            &input
+        };
+        if let Ok((_, translate)) = Translate::parse(input) {
+            transform.translate = translate;
+        }
+
+        Ok(transform)
+    }
+}
+
+impl Rotate {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        alt((
+            map(
+                delimited(tag("rotate("), preceded(char(' '), float), char(')')),
+                |angle| Self {
+                    angle,
+                    x: 0.0,
+                    y: 0.0,
+                },
+            ),
+            map(
+                delimited(
+                    tag("rotate("),
+                    tuple((
+                        float,
+                        preceded(char(' '), float),
+                        preceded(char(' '), float),
+                    )),
+                    char(')'),
+                ),
+                |(angle, x, y)| Self { angle, x, y },
+            ),
+        ))(input)
+    }
+
+    pub fn to_vec2(&self) -> Vec2 {
+        Vec2::new(self.x, self.y)
+    }
+}
+
+impl Display for Rotate {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.x == 0.0 && self.y == 0.0 {
+            write!(f, "rotate({})", self.angle)
+        } else {
+            write!(f, "rotate({} {} {})", self.angle, self.x, self.y)
+        }
+    }
+}
+
+impl Translate {
+    fn parse(input: &str) -> IResult<&str, Self> {
+        map(
+            delimited(
+                tag("translate("),
+                tuple((float, preceded(char(' '), float))),
+                char(')'),
+            ),
+            |(x, y)| Self { x, y },
+        )(input)
+    }
+
+    pub fn to_vec2(&self) -> Vec2 {
+        Vec2::new(self.x, self.y)
+    }
+}
+
+impl Display for Translate {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "translate({} {})", self.x, self.y)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Test {
+        #[serde(rename = "@transform")]
+        transform: Transform,
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = Test {
+            transform: Transform {
+                rotate: Rotate {
+                    angle: 3.15,
+                    x: 7.0,
+                    y: -1.23,
+                },
+                translate: Translate { x: 4.0, y: -1.4 },
+            },
+        };
+        let expected = "<test transform=\"rotate(3.15 7 -1.23)translate(4 -1.4)\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Test {
+            transform: Transform::default(),
+        };
+        let expected = "<test transform=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("test")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test = "<test transform=\"rotate(45.7 -43.21 89)translate(4 -1.4)\"/>";
+        let expected = Test {
+            transform: Transform {
+                rotate: Rotate {
+                    angle: 45.7,
+                    x: -43.21,
+                    y: 89.0,
+                },
+                translate: Translate { x: 4.0, y: -1.4 },
+            },
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<test transform=\"\"/>";
+        let expected = Test {
+            transform: Transform::default(),
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Test::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/elements.rs b/src/ui/panes/pid_new/svg/elements.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4b42c4d5a06c1809a74ef7641e6fe1ac9b441d25
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/elements.rs
@@ -0,0 +1,4 @@
+pub mod defs;
+pub mod path;
+pub mod text;
+pub mod use_node;
diff --git a/src/ui/panes/pid_new/svg/elements/defs.rs b/src/ui/panes/pid_new/svg/elements/defs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5b5b26100679d428e2abb18fb1446b17e67ceb55
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/elements/defs.rs
@@ -0,0 +1,14 @@
+use super::{path::Path, text::Text};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
+pub struct Defs {
+    #[serde(rename = "path")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub paths: Vec<Path>,
+
+    #[serde(rename = "text")]
+    #[serde(default)]
+    pub texts: Vec<Text>,
+}
diff --git a/src/ui/panes/pid_new/svg/elements/path.rs b/src/ui/panes/pid_new/svg/elements/path.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7bbc52d95353c85a3be937cedd5b41c195bbbda7
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/elements/path.rs
@@ -0,0 +1,97 @@
+use super::super::{
+    attributes::{data::Data, style::Style, transform::Transform},
+    utils::{is_default, is_zero},
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone)]
+pub struct Path {
+    #[serde(rename = "@id")]
+    pub id: String,
+
+    #[serde(rename = "@width")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub width: f32,
+
+    #[serde(rename = "@height")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub height: f32,
+
+    #[serde(rename = "@d")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub data: Data,
+
+    #[serde(rename = "@style")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub style: Style,
+
+    #[serde(rename = "@transfrom")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub transform: Transform,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn it_serialize() {
+        let test = Path {
+            id: "paolino".to_string(),
+            width: 23.4,
+            height: 5.0,
+            data: Data::default(),
+            style: Style::default(),
+            transform: Transform::default(),
+        };
+        let expected = "<path id=\"paolino\" width=\"23.4\" height=\"5\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("path")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Path::default();
+        let expected = "<path id=\"\"/>";
+
+        let mut buffer = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut buffer, Some("path")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(buffer, expected);
+    }
+
+    #[test]
+    fn it_deserialize() {
+        let test = "<test id=\"pepperoncino\" width=\"11\" height=\"24.5\"/>";
+        let expected = Path {
+            id: "pepperoncino".to_string(),
+            width: 11.0,
+            height: 24.5,
+            data: Data::default(),
+            style: Style::default(),
+            transform: Transform::default(),
+        };
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Path::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let test = "<path id=\"\"/>";
+        let expected = Path::default();
+
+        let mut des = quick_xml::de::Deserializer::from_str(test);
+        let deserialized = Path::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/elements/text.rs b/src/ui/panes/pid_new/svg/elements/text.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8181ef9ce5c2cf7051d1312054b2f154557b55dd
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/elements/text.rs
@@ -0,0 +1,43 @@
+use super::super::{
+    attributes::transform::Transform,
+    utils::{is_default, is_zero},
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+pub struct Text {
+    #[serde(rename = "@font-size")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub font_size: f32,
+
+    #[serde(rename = "@font-family")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub font_family: String,
+
+    #[serde(rename = "@transform")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub transform: Transform,
+
+    #[serde(rename = "@segs-format")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub format: String,
+
+    #[serde(rename = "$text")]
+    pub text: String,
+}
+
+impl Text {
+    pub fn new(text: String, size: f32) -> Self {
+        Self {
+            font_size: size,
+            font_family: "monospace".to_string(),
+            transform: Transform::default(),
+            format: "TODO".to_string(),
+            text,
+        }
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/elements/use_node.rs b/src/ui/panes/pid_new/svg/elements/use_node.rs
new file mode 100644
index 0000000000000000000000000000000000000000..14f139b9fe774420d3f5522ae1f610cb797081a6
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/elements/use_node.rs
@@ -0,0 +1,92 @@
+use super::super::{
+    attributes::transform::Transform,
+    utils::{is_default, is_zero},
+};
+use egui::{InputState, Pos2};
+use glam::Vec2;
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+pub struct Use {
+    #[serde(rename = "@href", with = "href")]
+    pub href: String,
+
+    #[serde(rename = "@width")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub width: f32,
+
+    #[serde(rename = "@height")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub height: f32,
+
+    #[serde(rename = "@transform")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub transform: Transform,
+}
+
+impl Use {
+    pub fn handle_click(&mut self, _pos: Pos2) {}
+    pub fn handle_double_click(&mut self, _pos: Pos2) {}
+
+    /// Consumes any shortcut the element should respond to
+    pub fn handle_shortcuts(&mut self, _input: &mut InputState) {}
+
+    pub fn hovered(&self, pos: Pos2) -> bool {
+        let pos = self.transform.to_local_frame(pos).to_vec2();
+
+        // The bounding box in the elemen's frame is defined by the size. But
+        // width and height can be negative. This allows to represent where the
+        // position anchor point is (for paths is the top-left, for texts
+        // is the bottom-left)
+        let size = Vec2::new(self.width, self.height);
+        let min = Vec2::ZERO.min(size);
+        let max = Vec2::ZERO.max(size);
+
+        // Check if the point is in the bounding box
+        min.x <= pos.x && pos.x <= max.x && min.y <= pos.y && pos.y <= max.y
+    }
+
+    /// Whether the elemen can be moved
+    pub fn draggable(&self) -> bool {
+        false
+    }
+
+    /// Whether a window to edit the element's configuration is available
+    pub fn editable(&self) -> bool {
+        false
+    }
+
+    pub fn who_am_i(&self) -> String {
+        format!(
+            "{} at x={} y={} width={} height={}",
+            self.href,
+            self.transform.translate.x,
+            self.transform.translate.y,
+            self.width,
+            self.height
+        )
+    }
+}
+
+mod href {
+    use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+    pub fn serialize<S>(id: &str, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        format!("#{id}").serialize(serializer)
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let mut id = String::deserialize(deserializer)?;
+        id.remove(0);
+        Ok(id)
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/mod.rs b/src/ui/panes/pid_new/svg/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c8634d3d38bc68886546124c00f66ce6b1964a54
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/mod.rs
@@ -0,0 +1,263 @@
+pub mod attributes;
+pub mod elements;
+pub mod utils;
+
+use attributes::data::{
+    close_path::ClosePath, horizonta_line_to::HorizontalLineTo, line_to::LineTo, move_to::MoveTo,
+    vertical_line_to::VerticalLineTo, Data,
+};
+use attributes::style::{LineJoin, Style};
+use attributes::transform::Transform;
+use egui::Color32;
+use elements::path::Path;
+use elements::{defs::Defs, use_node::Use};
+use serde::{Deserialize, Serialize};
+use utils::{is_default, is_zero};
+
+use super::{Element, Pid3};
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct Svg {
+    #[serde(rename = "@width")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub width: f32,
+
+    #[serde(rename = "@height")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_zero")]
+    pub height: f32,
+
+    #[serde(rename = "@version")]
+    pub version: f32, // 1.1
+
+    #[serde(rename = "@xmlns")]
+    pub xmlns: String, // http://www.w3.org/2000/svg
+
+    #[serde(rename = "defs")]
+    #[serde(default)]
+    #[serde(skip_serializing_if = "is_default")]
+    pub defs: Defs,
+
+    #[serde(rename = "use")]
+    #[serde(default)]
+    pub uses: Vec<Use>,
+}
+
+impl Default for Svg {
+    fn default() -> Self {
+        Self {
+            width: 0.0,
+            height: 0.0,
+            version: 1.1,
+            xmlns: "http://www.w3.org/2000/svg".to_string(),
+            defs: Defs::default(),
+            uses: Vec::new(),
+        }
+    }
+}
+
+impl From<&Pid3> for Svg {
+    fn from(pid: &Pid3) -> Self {
+        let mut svg = Self {
+            // TODO: Compute size
+            width: 10.0,
+            height: 10.0,
+            ..Default::default()
+        };
+
+        pid.elements.iter().for_each(|(id, element)| match element {
+            Element::Icon => {
+                svg.defs.paths.push(Path {
+                    id: id.clone(),
+                    width: 4.0,
+                    height: 4.0,
+                    data: Data {
+                        segments: vec![
+                            MoveTo::abs(0.7, 2.0),
+                            LineTo::rel(2.6, -1.5),
+                            VerticalLineTo::rel(3.0),
+                            ClosePath::token(),
+                            MoveTo::abs(0.0, 2.0),
+                            HorizontalLineTo::rel(4.0),
+                        ],
+                    },
+                    style: Style {
+                        stroke: Color32::BLACK,
+                        stroke_width: 0.2,
+                        stroke_linejoin: LineJoin::Round,
+                        ..Default::default()
+                    },
+                    ..Default::default()
+                });
+            }
+            Element::Label => {}
+        });
+
+        pid.references
+            .iter()
+            .flat_map(|r| Some(r).zip(pid.elements.get(&r.id)))
+            .for_each(|(r, _)| {
+                svg.uses.push(Use {
+                    href: r.id.clone(),
+                    width: 4.0,
+                    height: 4.0,
+                    transform: Transform::default(),
+                });
+            });
+
+        svg
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::{
+        attributes::{
+            data::ellicptical_arc::EllipticalArc,
+            transform::{Rotate, Transform, Translate},
+        },
+        elements::text::Text,
+        *,
+    };
+
+    fn test_svg() -> Svg {
+        Svg {
+            width: 14.0,
+            height: 17.196152,
+            defs: Defs {
+                paths: vec![
+                    Path {
+                        id: "arrow".to_string(),
+                        width: 4.0,
+                        height: 4.0,
+                        data: Data {
+                            segments: vec![
+                                MoveTo::abs(0.7, 2.0),
+                                LineTo::rel(2.6, -1.5),
+                                VerticalLineTo::rel(3.0),
+                                ClosePath::token(),
+                                MoveTo::abs(0.0, 2.0),
+                                HorizontalLineTo::rel(4.0),
+                            ],
+                        },
+                        style: Style {
+                            stroke: Color32::BLACK,
+                            stroke_width: 0.2,
+                            stroke_linejoin: LineJoin::Round,
+                            ..Default::default()
+                        },
+                        ..Default::default()
+                    },
+                    Path {
+                        id: "burst_disk".to_string(),
+                        width: 4.0,
+                        height: 6.0,
+                        data: Data {
+                            segments: vec![
+                                MoveTo::abs(0.5, 0.0),
+                                VerticalLineTo::abs(6.0),
+                                MoveTo::abs(1.5, 0.0),
+                                VerticalLineTo::rel(1.0),
+                                EllipticalArc::rel(2.0, 2.0, 0.0, true, true, 0.0, 4.0),
+                                VerticalLineTo::rel(1.0),
+                                MoveTo::abs(0.0, 3.0),
+                                HorizontalLineTo::rel(0.5),
+                                MoveTo::abs(3.5, 3.0),
+                                HorizontalLineTo::rel(0.5),
+                            ],
+                        },
+                        style: Style {
+                            stroke: Color32::BLACK,
+                            fill: Color32::TRANSPARENT,
+                            stroke_width: 0.2,
+                            stroke_linejoin: LineJoin::Round,
+                            ..Default::default()
+                        },
+                        ..Default::default()
+                    },
+                ],
+                texts: vec![Text {
+                    font_size: 2.0,
+                    font_family: "monospace".to_string(),
+                    transform: Transform {
+                        translate: Translate { x: 1.0, y: 3.0 },
+                        ..Default::default()
+                    },
+                    format: "{:.2f}".to_string(),
+                    text: "Hi mom!".to_string(),
+                }],
+            },
+            uses: vec![
+                Use {
+                    href: "arrow".to_string(),
+                    width: 4.0,
+                    height: 4.0,
+                    transform: Transform {
+                        rotate: Rotate {
+                            angle: 180.0,
+                            x: 7.0,
+                            y: 8.0,
+                        },
+                        translate: Translate { x: 5.0, y: 6.0 },
+                    },
+                },
+                Use {
+                    href: "burst_disk".to_string(),
+                    width: 4.0,
+                    height: 6.0,
+                    transform: Transform {
+                        translate: Translate { x: 1.0, y: 5.0 },
+                        ..Default::default()
+                    },
+                },
+            ],
+            ..Default::default()
+        }
+    }
+
+    #[test]
+    fn it_serialize() {
+        let test = test_svg();
+        let expected =
+            String::from_utf8(std::fs::read("test_assets/simple_pid.svg").unwrap()).unwrap();
+
+        let mut serialized = String::new();
+        let mut ser = quick_xml::se::Serializer::with_root(&mut serialized, Some("svg")).unwrap();
+        ser.indent(' ', 4);
+        test.serialize(ser).unwrap();
+        assert_eq!(serialized, expected);
+    }
+
+    #[test]
+    fn it_serialize_default() {
+        let test = Svg::default();
+        let expected = "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"/>";
+
+        let mut serialized = String::new();
+        let ser = quick_xml::se::Serializer::with_root(&mut serialized, Some("svg")).unwrap();
+        test.serialize(ser).unwrap();
+        assert_eq!(serialized, expected);
+    }
+
+    #[test]
+    fn it_deserializes() {
+        let test = String::from_utf8(std::fs::read("test_assets/simple_pid.svg").unwrap()).unwrap();
+        let expected = test_svg();
+
+        let mut des = quick_xml::de::Deserializer::from_str(&test);
+        let deserialized = Svg::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+
+    #[test]
+    fn it_deserialize_default() {
+        let svg = "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"/>";
+        let expected = Svg::default();
+
+        let mut des = quick_xml::de::Deserializer::from_str(svg);
+        let deserialized = Svg::deserialize(&mut des).unwrap();
+        assert_eq!(deserialized, expected);
+    }
+}
diff --git a/src/ui/panes/pid_new/svg/utils.rs b/src/ui/panes/pid_new/svg/utils.rs
new file mode 100644
index 0000000000000000000000000000000000000000..16be55525989331c0a3a2c05c4ee320d927020a0
--- /dev/null
+++ b/src/ui/panes/pid_new/svg/utils.rs
@@ -0,0 +1,7 @@
+pub fn is_default<T: Default + PartialEq>(x: &T) -> bool {
+    *x == T::default()
+}
+
+pub fn is_zero(x: &f32) -> bool {
+    *x == 0.0
+}
diff --git a/src/ui/widget_gallery.rs b/src/ui/widget_gallery.rs
index a5288191848c04212c0845c403ad12b1c8d1ea31..80ff45596812d5f81fad72ad53fc6dfed891d61d 100644
--- a/src/ui/widget_gallery.rs
+++ b/src/ui/widget_gallery.rs
@@ -4,7 +4,7 @@ use strum::{EnumMessage, IntoEnumIterator};
 
 use super::{
     composable_view::PaneAction,
-    panes::{Pane, PaneKind},
+    panes::{pid::Pid2, Pane, PaneKind},
 };
 
 #[derive(Default)]
@@ -28,6 +28,16 @@ impl WidgetGallery {
                 for pane in PaneKind::iter() {
                     if let PaneKind::Default(_) = pane {
                         continue;
+                    } else if let PaneKind::Pid(_) = pane {
+                        let pid = Pid2::from_file();
+                        if ui.button("PID").clicked() {
+                            if let Some(tile_id) = self.tile_id {
+                                return Some(PaneAction::Replace(
+                                    tile_id,
+                                    Pane::boxed(PaneKind::Pid(pid)),
+                                ));
+                            }
+                        }
                     } else if let Some(message) = pane.get_message() {
                         if ui.button(message).clicked() {
                             if let Some(tile_id) = self.tile_id {
diff --git a/test_assets/simple_pid.svg b/test_assets/simple_pid.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bb0a40a22f01801300c8d6b6fc3925d779830091
--- /dev/null
+++ b/test_assets/simple_pid.svg
@@ -0,0 +1,9 @@
+<svg width="14" height="17.196152" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <path id="arrow" width="4" height="4" d="M 0.7 2 l 2.6 -1.5 v 3 Z M 0 2 h 4" style="stroke:#000000ff;stroke-width:0.2;stroke-linejoin:round"/>
+        <path id="burst_disk" width="4" height="6" d="M 0.5 0 V 6 M 1.5 0 v 1 a 2 2 0 1 1 0 4 v 1 M 0 3 h 0.5 M 3.5 3 h 0.5" style="fill:#00000000;stroke:#000000ff;stroke-width:0.2;stroke-linejoin:round"/>
+    </defs>
+    <use href="#arrow" width="4" height="4" transform="rotate(180 7 8)translate(5 6)"/>
+    <use href="#burst_disk" width="4" height="6" transform="translate(1 5)"/>
+    <text font-size="2" transform="translate(1 3)" segs-format="{:.2f}">Hi mom!</text>
+</svg>
diff --git a/test_assets/simple_text.svg b/test_assets/simple_text.svg
new file mode 100644
index 0000000000000000000000000000000000000000..764e477a65803785edfee24c74390e2461a96050
--- /dev/null
+++ b/test_assets/simple_text.svg
@@ -0,0 +1,7 @@
+<svg width="200" height="200" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <path id="line" style="stroke:#000000ff;stroke-width:5" d="M 10 10 L 200 200"/>
+    </defs>
+    <use href="#line"/>
+    <text transform="translate(100 100)" font-size="20" font-family="monospace">Your text here</text>
+</svg>