diff --git a/addons/plotting/$PBOPREFIX$ b/addons/plotting/$PBOPREFIX$
new file mode 100644
index 000000000..d5de66d5e
--- /dev/null
+++ b/addons/plotting/$PBOPREFIX$
@@ -0,0 +1 @@
+x\zen\addons\plotting
diff --git a/addons/plotting/CfgContext.hpp b/addons/plotting/CfgContext.hpp
new file mode 100644
index 000000000..8b5817734
--- /dev/null
+++ b/addons/plotting/CfgContext.hpp
@@ -0,0 +1,39 @@
+class EGVAR(context_menu,actions) {
+    class Plots {
+        displayName = CSTRING(DisplayName);
+        icon = QPATHTOF(ui\ruler.paa);
+        statement = QUOTE(call FUNC(selectPosition));
+        args = "LINE";
+        priority = 15;
+
+        class MeasureDistance {
+            displayName = CSTRING(MeasureDistance);
+            icon = QPATHTOF(ui\ruler.paa);
+            statement = QUOTE(call FUNC(selectPosition));
+            args = "LINE";
+        };
+        class MeasureDistanceFromCamera {
+            displayName = CSTRING(MeasureDistanceFromCamera);
+            icon = QPATHTOF(ui\ruler.paa);
+            statement = QUOTE([ARR_2(_args,curatorCamera)] call FUNC(setActivePlot));
+            args = "LINE";
+        };
+        class MeasureRadius {
+            displayName = CSTRING(MeasureRadius);
+            icon = QPATHTOF(ui\radius.paa);
+            statement = QUOTE(call FUNC(selectPosition));
+            args = "RADIUS";
+        };
+        class MeasureOffset {
+            displayName = CSTRING(MeasureOffset);
+            icon = QPATHTOF(ui\cuboid.paa);
+            statement = QUOTE(call FUNC(selectPosition));
+            args = "RECTANGLE";
+        };
+        class ClearPlots {
+            displayName = CSTRING(ClearPlots);
+            icon = "A3\3den\Data\Displays\Display3DEN\PanelLeft\entityList_delete_ca.paa";
+            statement = QUOTE(call FUNC(clearPlots));
+        };
+    };
+};
diff --git a/addons/plotting/CfgEventHandlers.hpp b/addons/plotting/CfgEventHandlers.hpp
new file mode 100644
index 000000000..f6503c247
--- /dev/null
+++ b/addons/plotting/CfgEventHandlers.hpp
@@ -0,0 +1,17 @@
+class Extended_PreStart_EventHandlers {
+    class ADDON {
+        init = QUOTE(call COMPILE_SCRIPT(XEH_preStart));
+    };
+};
+
+class Extended_PreInit_EventHandlers {
+    class ADDON {
+        init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
+    };
+};
+
+class Extended_PostInit_EventHandlers {
+    class ADDON {
+        init = QUOTE(call COMPILE_SCRIPT(XEH_postInit));
+    };
+};
diff --git a/addons/plotting/CfgFormatters.hpp b/addons/plotting/CfgFormatters.hpp
new file mode 100644
index 000000000..ac3c7420a
--- /dev/null
+++ b/addons/plotting/CfgFormatters.hpp
@@ -0,0 +1,30 @@
+class GVAR(formatters) {
+    class Distance {
+        class Meter {
+            formatter = QUOTE(format [ARR_2('%1 m',_value toFixed 1)]);
+            priority = 100;
+        };
+        class Feet {
+            formatter = QUOTE(format [ARR_2('%1 ft',(_value * 3.281) toFixed 1)]);
+            priority = 90;
+        };
+        class Yards {
+            formatter = QUOTE(format [ARR_2('%1 yd',(_value * 1.094) toFixed 1)]);
+            priority = 80;
+        };
+        class Mile {
+            formatter = QUOTE(format [ARR_2('%1 mi',(_value / 1609.344) toFixed 2)]);
+            priority = 70;
+        };
+    };
+    class Azimuth {
+        class Degree {
+            formatter = QUOTE(format [ARR_2('%1°',_value toFixed 1)]);
+            priority = 100;
+        };
+        class NATOMil {
+            formatter = QUOTE(format [ARR_2('%1 mil',(_value * 17.7778) toFixed 0)]);
+            priority = 90;
+        };
+    };
+};
diff --git a/addons/plotting/XEH_PREP.hpp b/addons/plotting/XEH_PREP.hpp
new file mode 100644
index 000000000..74836fd4c
--- /dev/null
+++ b/addons/plotting/XEH_PREP.hpp
@@ -0,0 +1,16 @@
+PREP(addPlot);
+PREP(clearPlots);
+PREP(compileFormatters);
+PREP(drawLine);
+PREP(drawPlots);
+PREP(drawRadius);
+PREP(drawRectangle);
+PREP(isPosInCylinder);
+PREP(onDraw);
+PREP(onDraw3D);
+PREP(onKeyDown);
+PREP(onLoad);
+PREP(onMouseButtonDown);
+PREP(onUnload);
+PREP(selectPosition);
+PREP(setActivePlot);
diff --git a/addons/plotting/XEH_postInit.sqf b/addons/plotting/XEH_postInit.sqf
new file mode 100644
index 000000000..cf18ef9b4
--- /dev/null
+++ b/addons/plotting/XEH_postInit.sqf
@@ -0,0 +1,12 @@
+#include "script_component.hpp"
+
+if (hasInterface) then {
+    // All plots ordered by creation time, last is newest
+    GVAR(plots) = [];
+
+    ["zen_curatorDisplayLoaded", LINKFUNC(onLoad)] call CBA_fnc_addEventHandler;
+    ["zen_curatorDisplayUnloaded", LINKFUNC(onUnload)] call CBA_fnc_addEventHandler;
+
+    [QGVAR(plotAdded), LINKFUNC(addPlot)] call CBA_fnc_addEventHandler;
+    [QGVAR(plotsCleared), LINKFUNC(clearPlots)] call CBA_fnc_addEventHandler;
+};
diff --git a/addons/plotting/XEH_preInit.sqf b/addons/plotting/XEH_preInit.sqf
new file mode 100644
index 000000000..b9fd02bb9
--- /dev/null
+++ b/addons/plotting/XEH_preInit.sqf
@@ -0,0 +1,28 @@
+#include "script_component.hpp"
+
+ADDON = false;
+
+PREP_RECOMPILE_START;
+#include "XEH_PREP.hpp"
+PREP_RECOMPILE_END;
+
+if (hasInterface) then {
+    GVAR(activePlot) = [];
+    GVAR(draw3DAdded) = false;
+
+    GVAR(plotTypes) = createHashMapFromArray [
+        ["LINE", LINKFUNC(drawLine)],
+        ["RADIUS", LINKFUNC(drawRadius)],
+        ["RECTANGLE", LINKFUNC(drawRectangle)]
+    ];
+
+    GVAR(currentDistanceFormatter) = 0;
+    GVAR(currentAzimuthFormatter) = 0;
+};
+
+call FUNC(compileFormatters);
+
+#include "initSettings.inc.sqf"
+#include "initKeybinds.inc.sqf"
+
+ADDON = true;
diff --git a/addons/plotting/XEH_preStart.sqf b/addons/plotting/XEH_preStart.sqf
new file mode 100644
index 000000000..022888575
--- /dev/null
+++ b/addons/plotting/XEH_preStart.sqf
@@ -0,0 +1,3 @@
+#include "script_component.hpp"
+
+#include "XEH_PREP.hpp"
diff --git a/addons/plotting/config.cpp b/addons/plotting/config.cpp
new file mode 100644
index 000000000..991023a13
--- /dev/null
+++ b/addons/plotting/config.cpp
@@ -0,0 +1,21 @@
+#include "script_component.hpp"
+
+class CfgPatches {
+    class ADDON {
+        name = COMPONENT_NAME;
+        units[] = {};
+        weapons[] = {};
+        requiredVersion = REQUIRED_VERSION;
+        requiredAddons[] = {"zen_common", "zen_context_menu"};
+        author = ECSTRING(main,Author);
+        authors[] = {"Timi007"};
+        url = ECSTRING(main,URL);
+        VERSION_CONFIG;
+    };
+};
+
+PRELOAD_ADDONS;
+
+#include "CfgEventHandlers.hpp"
+#include "CfgContext.hpp"
+#include "CfgFormatters.hpp"
diff --git a/addons/plotting/functions/fnc_addPlot.sqf b/addons/plotting/functions/fnc_addPlot.sqf
new file mode 100644
index 000000000..051e490fc
--- /dev/null
+++ b/addons/plotting/functions/fnc_addPlot.sqf
@@ -0,0 +1,28 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Adds a new plot to be drawn.
+ *
+ * Arguments:
+ * 0: Type of plot <STRING>
+ * 1: Start position ASL or attached object <ARRAY or OBJECT>
+ * 2: End position ASL or attached object <ARRAY or OBJECT>
+ *
+ * Return Value:
+ * Index of new plot <NUMBER>
+ *
+ * Example:
+ * ["RADIUS", player, [100, 100, 10]] call zen_plotting_fnc_addPlot
+ *
+ * Public: No
+ */
+
+params [
+    ["_type", "LINE", [""]],
+    ["_startPos", [0, 0, 0], [[], objNull], [3]],
+    ["_endPos", [0, 0, 0], [[], objNull], [3]]
+];
+
+if !(_type in GVAR(plotTypes)) exitWith {-1};
+
+GVAR(plots) pushBack [_type, _startPos, _endPos]
diff --git a/addons/plotting/functions/fnc_clearPlots.sqf b/addons/plotting/functions/fnc_clearPlots.sqf
new file mode 100644
index 000000000..fc1175238
--- /dev/null
+++ b/addons/plotting/functions/fnc_clearPlots.sqf
@@ -0,0 +1,18 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Clears of plots.
+ *
+ * Arguments:
+ * None
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [] call zen_plotting_fnc_clearPlots
+ *
+ * Public: No
+ */
+
+GVAR(plots) = [];
diff --git a/addons/plotting/functions/fnc_compileFormatters.sqf b/addons/plotting/functions/fnc_compileFormatters.sqf
new file mode 100644
index 000000000..ab3b6df32
--- /dev/null
+++ b/addons/plotting/functions/fnc_compileFormatters.sqf
@@ -0,0 +1,52 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Compiles formatters from config and saves them in missionNamespace.
+ *
+ * Arguments:
+ * None
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [] call zen_plotting_fnc_compileFormatters
+ *
+ * Public: No
+ */
+
+private _fnc_getFormatters = {
+    params ["_cfgFormatters"];
+
+    private _formatters = [];
+    {
+        private _entryConfig = _x;
+
+        private _formatterName = configName _entryConfig;
+
+        private _formatterString = getText (_entryConfig >> "formatter");
+        if (_formatterString isNotEqualTo "") then {
+            _formatterString = "params ['_value']; " + _formatterString;
+        };
+        private _formatter = compile _formatterString;
+
+        private _priority = getNumber (_entryConfig >> "priority");
+
+        private _formatterEntry = [
+            _formatterName,
+            _formatter,
+            _priority
+        ];
+
+        _formatters pushBack _formatterEntry;
+    } forEach configProperties [_cfgFormatters, "isClass _x", true];
+
+    [_formatters, 3, false] call CBA_fnc_sortNestedArray
+};
+
+private _cfgFormatters = configFile >> QGVAR(formatters);
+private _distanceFormatters = [_cfgFormatters >> "Distance"] call _fnc_getFormatters;
+private _azimuthFormatters = [_cfgFormatters >> "Azimuth"] call _fnc_getFormatters;
+
+missionNamespace setVariable [QGVAR(distanceFormatters), _distanceFormatters];
+missionNamespace setVariable [QGVAR(azimuthFormatters), _azimuthFormatters];
diff --git a/addons/plotting/functions/fnc_drawLine.sqf b/addons/plotting/functions/fnc_drawLine.sqf
new file mode 100644
index 000000000..d6154b3c3
--- /dev/null
+++ b/addons/plotting/functions/fnc_drawLine.sqf
@@ -0,0 +1,63 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Draws a line plot in 3D or on the map. Must be called every frame.
+ *
+ * Arguments:
+ * 0: Start position ASL <ARRAY>
+ * 1: End position ASL <ARRAY>
+ * 2: Visual properties <ARRAY>
+ *      0: Icon <STRING>
+ *      1: Color RGBA <ARRAY>
+ *      2: Scale <NUMBER>
+ *      3: Angle <NUMBER>
+ *      4: Line width <NUMBER>
+ * 3: Formatters <ARRAY>
+ *      0: Distance formatter <CODE>
+ *      1: Azimuth formatter <CODE>
+ * 4: Camera position ASL <ARRAY> (default: Don't check render distance)
+ * 5: Map control <CONTROL> (default: Draw in 3D)
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [[0, 0, 0], [100, 100, 0], ["", [1, 0, 0, 1], 1, 0, 5], [{_this toFixed 0}, {_this toFixed 1}]] call zen_plotting_fnc_drawLine
+ *
+ * Public: No
+ */
+
+params ["_startPos", "_endPos", "_visualProperties", "_formatters", ["_ctrlMap", controlNull, [controlNull]]];
+_visualProperties params ["_icon", "_color", "_scale", "_angle", "_lineWidth"];
+
+private _distance = _startPos vectorDistance _endPos;
+
+private _azimuthToStart = _endPos getDir _startPos;
+private _azimuthToEnd = _startPos getDir _endPos;
+
+private _fnc_format = {
+    params ["_distance", "_azimuth", "_formatters"];
+    _formatters params ["_fnc_distanceFormatter", "_fnc_azimuthFormatter"];
+
+    format ["%1 - %2", _distance call _fnc_distanceFormatter, _azimuth call _fnc_azimuthFormatter]
+};
+
+if (isNull _ctrlMap) then { // 3D
+    private _camPos = getPosASL curatorCamera;
+
+    if (CAN_RENDER_ICON(_camPos,_startPos)) then {
+        drawIcon3D [_icon, _color, ASLToAGL _startPos, _scale, _scale, _angle, [_distance, _azimuthToStart, _formatters] call _fnc_format];
+    };
+
+    if (CAN_RENDER_LINE(_camPos,_startPos,_endPos)) then {
+        drawLine3D [ASLToAGL _startPos, ASLToAGL _endPos, _color, _lineWidth];
+    };
+
+    if (CAN_RENDER_ICON(_camPos,_endPos)) then {
+        drawIcon3D [_icon, _color, ASLToAGL _endPos, _scale, _scale, _angle, [_distance, _azimuthToEnd, _formatters] call _fnc_format];
+    };
+} else { // Map
+    _ctrlMap drawIcon [_icon, _color, _startPos, _scale, _scale, _angle, [_distance, _azimuthToStart, _formatters] call _fnc_format];
+    _ctrlMap drawLine [_startPos, _endPos, _color];
+    _ctrlMap drawIcon [_icon, _color, _endPos, _scale, _scale, _angle, [_distance, _azimuthToEnd, _formatters] call _fnc_format];
+};
diff --git a/addons/plotting/functions/fnc_drawPlots.sqf b/addons/plotting/functions/fnc_drawPlots.sqf
new file mode 100644
index 000000000..8a66cbf1f
--- /dev/null
+++ b/addons/plotting/functions/fnc_drawPlots.sqf
@@ -0,0 +1,82 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Draws all placed plots and currently active one in 3D or on the map. Must be called every frame.
+ *
+ * Arguments:
+ * 0: Permanent plots in format [type, startPosASLOrObj, endPosASLOrObj] <ARRAY>
+ * 1: Currently active plot in format [type, startPosASLOrObj] <ARRAY>
+ * 2: Zeus map <CONTROL> (default: Draw in 3D)
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [[["LINE", [0, 0, 0], player]], ["LINE", [100, 100, 0]]] call zen_plotting_fnc_drawPlots
+ *
+ * Public: No
+ */
+
+params ["_plots", ["_activePlot", [], [[]]], ["_ctrlMap", controlNull, [controlNull]]];
+
+private _drawIn3D = isNull _ctrlMap;
+
+private _d = 0;
+private _scale = 0;
+private _screenPos = [];
+
+private _formatters = [
+    (GVAR(distanceFormatters) select GVAR(currentDistanceFormatter)) select 1,
+    (GVAR(azimuthFormatters) select GVAR(currentAzimuthFormatter)) select 1
+];
+
+// Format active plot as permanent plot with mouse position as end position
+private _activePlotWithMouseEndPos = [];
+if (_activePlot isNotEqualTo []) then {
+    _activePlot params ["_type", "_startPosOrObj"];
+
+    private _endPos = if (_drawIn3D) then {
+        [EGVAR(common,mousePos), 2] call EFUNC(common,getPosFromScreen)
+    } else {
+        private _pos2D = _ctrlMap ctrlMapScreenToWorld getMousePosition;
+        _pos2D set [2, getTerrainHeightASL _pos2D];
+        _pos2D
+    };
+
+    _activePlotWithMouseEndPos = [[_type, _startPosOrObj, _endPos]];
+};
+
+// Draw all plots
+{
+    _x params ["_type", "_startPosOrObj", "_endPosOrObj"];
+
+    private _startPos = _startPosOrObj;
+    if (_startPosOrObj isEqualType objNull) then {
+        if (isNull _startPosOrObj) then {continue};
+
+        _startPos = getPosASLVisual _startPosOrObj;
+    };
+
+    private _endPos = _endPosOrObj;
+    if (_endPosOrObj isEqualType objNull) then {
+        if (isNull _endPosOrObj) then {continue};
+
+        _endPos = getPosASLVisual _endPosOrObj;
+    };
+
+    if (_drawIn3D) then {
+        _scale = ICON_SCALE;
+    } else {
+        _scale = MAP_ICON_SCALE;
+    };
+
+    if (_scale < 0.01) then {
+        continue;
+    };
+
+    private _visualProperties = [ICON, GVAR(color), _scale, ICON_ANGLE, LINEWIDTH];
+    private _drawArgs = [_startPos, _endPos, _visualProperties, _formatters, _ctrlMap];
+    if (_type in GVAR(plotTypes)) then {
+        _drawArgs call (GVAR(plotTypes) get _type);
+    };
+} forEach (_activePlotWithMouseEndPos + _plots);
diff --git a/addons/plotting/functions/fnc_drawRadius.sqf b/addons/plotting/functions/fnc_drawRadius.sqf
new file mode 100644
index 000000000..38fc9883e
--- /dev/null
+++ b/addons/plotting/functions/fnc_drawRadius.sqf
@@ -0,0 +1,79 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Draws a radius circle plot in 3D or on the map. Must be called every frame.
+ *
+ * Arguments:
+ * 0: Center position ASL <ARRAY>
+ * 1: End position ASL <ARRAY>
+ * 2: Visual properties <ARRAY>
+ *      0: Icon <STRING>
+ *      1: Color RGBA <ARRAY>
+ *      2: Scale <NUMBER>
+ *      3: Angle <NUMBER>
+ *      4: Line width <NUMBER>
+ * 3: Formatters <ARRAY>
+ *      0: Distance formatter <CODE>
+ *      1: Azimuth formatter <CODE>
+ * 3: Map control <CONTROL> (default: Draw in 3D)
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [[0, 0, 0], [100, 100, 0], ["", [1, 0, 0, 1], 1, 0, 5], [{_this toFixed 0}, {_this toFixed 1}]] call zen_plotting_fnc_drawRadius
+ *
+ * Public: No
+ */
+
+params ["_centerPos", "_endPos", "_visualProperties", "_formatters", ["_ctrlMap", controlNull, [controlNull]]];
+_visualProperties params ["_icon", "_color", "_scale", "_angle", "_lineWidth"];
+
+private _radius = _centerPos vectorDistance _endPos;
+private _azimuth = _centerPos getDir _endPos;
+
+private _fnc_format = {
+    params ["_distance", "_azimuth", "_formatters"];
+    _formatters params ["_fnc_distanceFormatter", "_fnc_azimuthFormatter"];
+
+    format ["%1 - %2", _distance call _fnc_distanceFormatter, _azimuth call _fnc_azimuthFormatter]
+};
+
+if (isNull _ctrlMap) then { // 3D
+    private _camPos = getPosASL curatorCamera;
+
+    private _centerAGL = ASLToAGL _centerPos;
+    private _endPosAGL = ASLToAGL _endPos;
+
+    if (CAN_RENDER_ICON(_camPos,_centerPos)) then {
+        drawIcon3D [_icon, _color, _centerAGL, _scale, _scale, _angle];
+    };
+
+    if (_camPos vectorDistance _centerPos <= _radius + MAX_RENDER_DISTANCE) then {
+        drawLine3D [_centerAGL, _endPosAGL, _color, _lineWidth];
+        drawIcon3D [_icon, _color, _endPosAGL, _scale, _scale, _angle, [_radius, _azimuth, _formatters] call _fnc_format];
+    };
+
+    private _count = CIRCLE_EDGES_MIN max floor (2 * pi * _radius ^ 0.65 / CIRCLE_RESOLUTION);
+    private _factor = 360 / _count;
+    private _offsets = [];
+
+    for "_i" from 0 to (_count - 1) do {
+        private _phi = _i * _factor - _azimuth;
+        _offsets pushBack [_radius * cos _phi, _radius * sin _phi, 0];
+    };
+
+    for "_i" from 0 to (_count - 1) do {
+        private _pos1 = _centerPos vectorAdd (_offsets select _i);
+        private _pos2 = _centerPos vectorAdd (_offsets select ((_i + 1) % _count));
+
+        if (CAN_RENDER_LINE(_camPos,_pos1,_pos2)) then {
+            drawLine3D [ASLToAGL _pos1, ASLToAGL _pos2, _color, _lineWidth];
+        };
+    };
+} else { // Map
+    _ctrlMap drawIcon [_icon, _color, _centerPos, _scale, _scale, _angle];
+    _ctrlMap drawLine [_centerPos, _endPos, _color];
+    _ctrlMap drawEllipse [_centerPos, _radius, _radius, 0, _color, ""];
+    _ctrlMap drawIcon [_icon, _color, _endPos, _scale, _scale, _angle, [_radius, _azimuth, _formatters] call _fnc_format];
+};
diff --git a/addons/plotting/functions/fnc_drawRectangle.sqf b/addons/plotting/functions/fnc_drawRectangle.sqf
new file mode 100644
index 000000000..f25f6d93c
--- /dev/null
+++ b/addons/plotting/functions/fnc_drawRectangle.sqf
@@ -0,0 +1,86 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Draws a rectangle plot in 3D or on the map. Must be called every frame.
+ *
+ * Arguments:
+ * 0: Start position ASL <ARRAY>
+ * 1: End position ASL <ARRAY>
+ * 2: Visual properties <ARRAY>
+ *      0: Icon <STRING>
+ *      1: Color RGBA <ARRAY>
+ *      2: Scale <NUMBER>
+ *      3: Angle <NUMBER>
+ *      4: Line width <NUMBER>
+ * 3: Formatters <ARRAY>
+ *      0: Distance formatter <CODE>
+ * 3: Map control <CONTROL> (default: Draw in 3D)
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [[0, 0, 0], [100, 100, 0], ["", [1, 0, 0, 1], 1, 0, 5], [{_this toFixed 0}]] call zen_plotting_fnc_drawRectangle
+ *
+ * Public: No
+ */
+
+params ["_startPos", "_endPos", "_visualProperties", "_formatters", ["_ctrlMap", controlNull, [controlNull]]];
+_visualProperties params ["_icon", "_color", "_scale", "_angle", "_lineWidth"];
+
+private _offset = _endPos vectorDiff _startPos;
+_offset params ["_a", "_b", "_c"];
+
+private _fnc_format = {
+    params ["_offset", "_formatters"];
+    _offset params ["_a", "_b", "_c"];
+    _formatters params ["_fnc_distanceFormatter"];
+
+    format ["X: %1 - Y: %2 - Z: %3", _a call _fnc_distanceFormatter, _b call _fnc_distanceFormatter, _c call _fnc_distanceFormatter]
+};
+
+if (isNull _ctrlMap) then { // 3D
+    private _camPos = getPosASL curatorCamera;
+
+    if (CAN_RENDER_ICON(_camPos,_startPos)) then {
+        drawIcon3D [_icon, _color, ASLToAGL _startPos, _scale, _scale, _angle];
+    };
+
+    _startPos params ["_x1", "_y1", "_z1"];
+    _endPos params ["_x2", "_y2", "_z2"];
+
+    private _edges = if ((abs (_z2 - _z1)) > CUBOID_HEIGHT_THRESHOLD) then {
+        [
+            [[_x1, _y1, _z1], [_x2, _y1, _z1], [_x2, _y2, _z1], [_x1, _y2, _z1], [_x1, _y1, _z1]], // Rectangle same height as start pos
+            [[_x1, _y1, _z2], [_x2, _y1, _z2], [_x2, _y2, _z2], [_x1, _y2, _z2], [_x1, _y1, _z2]], // Rectangle same height as end pos
+            // Connections from start to end height
+            [[_x1, _y1, _z1], [_x1, _y1, _z2]],
+            [[_x2, _y1, _z1], [_x2, _y1, _z2]],
+            [[_x2, _y2, _z1], [_x2, _y2, _z2]],
+            [[_x1, _y2, _z1], [_x1, _y2, _z2]]
+        ]
+    } else {
+        // Don't draw cuboid if height difference is small
+        [[[_x1, _y1, _z1], [_x2, _y1, _z1], [_x2, _y2, _z1], [_x1, _y2, _z1], [_x1, _y1, _z1]]]
+    };
+
+    {
+        for "_i" from 0 to (count _x - 2) do {
+            private _pos1 = _x select _i;
+            private _pos2 = _x select (_i + 1);
+
+            if (CAN_RENDER_LINE(_camPos,_pos1,_pos2)) then {
+                drawLine3D [ASLToAGL _pos1, ASLToAGL _pos2, _color, _lineWidth];
+            };
+        };
+    } forEach _edges;
+
+    if (CAN_RENDER_ICON(_camPos,_endPos)) then {
+        drawIcon3D [_icon, _color, ASLToAGL _endPos, _scale, _scale, _angle, [_offset, _formatters] call _fnc_format];
+    };
+} else { // Map
+    _ctrlMap drawIcon [_icon, _color, _startPos, _scale, _scale, _angle];
+    private _center = (_startPos vectorAdd _endPos) vectorMultiply 0.5;
+    _ctrlMap drawRectangle [_center, _a / 2, _b / 2, 0, _color, ""];
+    _ctrlMap drawIcon [_icon, _color, _endPos, _scale, _scale, _angle, [_offset, _formatters] call _fnc_format];
+};
diff --git a/addons/plotting/functions/fnc_isPosInCylinder.sqf b/addons/plotting/functions/fnc_isPosInCylinder.sqf
new file mode 100644
index 000000000..16607f42a
--- /dev/null
+++ b/addons/plotting/functions/fnc_isPosInCylinder.sqf
@@ -0,0 +1,28 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Checks if the given point is inside the defined cylinder.
+ *
+ * Arguments:
+ * 0: Start position of cylinder <ARRAY>
+ * 1: End position of cylinder <ARRAY>
+ * 2: Radius of cylinder <NUMBER>
+ * 3: Point to check <ARRAY>
+ *
+ * Return Value:
+ * Position is in cylinder <BOOL>
+ *
+ * Example:
+ * [[0, 0, 0], [100, 0, 0], 50, [50, 50, 0]] call zen_plotting_fnc_isPosInCylinder
+ *
+ * Public: No
+ */
+
+params ["_p1", "_p2", "_r", "_q"];
+// https://stackoverflow.com/a/47933302
+
+private _diff = _p2 vectorDiff _p1;
+
+((_q vectorDiff _p1) vectorDotProduct _diff >= 0)    // First test (q - p1) * (p2 - p1) >= 0
+&& {(_q vectorDiff _p2) vectorDotProduct _diff <= 0} // Second test (q - p2) * (p2 - p1) <= 0
+&& {(vectorMagnitude ((_q vectorDiff _p1) vectorCrossProduct _diff)) <= (_r * vectorMagnitude _diff)} // Third test |(q - p1) x (p2 - p1)| <= r * |p2 - p1|
diff --git a/addons/plotting/functions/fnc_onDraw.sqf b/addons/plotting/functions/fnc_onDraw.sqf
new file mode 100644
index 000000000..ad71549a0
--- /dev/null
+++ b/addons/plotting/functions/fnc_onDraw.sqf
@@ -0,0 +1,26 @@
+#include "script_component.hpp"
+/*
+ * Author: Timi007
+ * Handles drawing the plots on the Zeus map. Is only called when map is open.
+ *
+ * Arguments:
+ * 0: Zeus map <CONTROL>
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [_ctrlMap] call zen_plotting_fnc_onDraw
+ *
+ * Public: No
+ */
+
+BEGIN_COUNTER(onDraw);
+
+params ["_ctrlMap"];
+
+if (dialog || {call EFUNC(common,isInScreenshotMode)}) exitWith {}; // Dialog is open or HUD is hidden
+
+[GVAR(plots), GVAR(activePlot), _ctrlMap] call FUNC(drawPlots);
+
+END_COUNTER(onDraw);
diff --git a/addons/plotting/functions/fnc_onDraw3D.sqf b/addons/plotting/functions/fnc_onDraw3D.sqf
new file mode 100644
index 000000000..4c06ac1f1
--- /dev/null
+++ b/addons/plotting/functions/fnc_onDraw3D.sqf
@@ -0,0 +1,29 @@
+#include "script_component.hpp"
+/*
+ * Author: Timi007
+ * Handles drawing the plots in Zeus 3D.
+ *
+ * Arguments:
+ * None
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * call zen_plotting_fnc_onDraw3D
+ *
+ * Public: No
+ */
+
+BEGIN_COUNTER(onDraw3D);
+
+if (
+    isNull (findDisplay IDD_RSCDISPLAYCURATOR)  // We are in not Zeus
+    || {!isNull (findDisplay IDD_INTERRUPT)}    // Pause menu is opened
+    || {dialog}                                 // We have a dialog open
+    || {call EFUNC(common,isInScreenshotMode)}  // HUD is hidden
+) exitWith {};
+
+[GVAR(plots), GVAR(activePlot)] call FUNC(drawPlots);
+
+END_COUNTER(onDraw3D);
diff --git a/addons/plotting/functions/fnc_onKeyDown.sqf b/addons/plotting/functions/fnc_onKeyDown.sqf
new file mode 100644
index 000000000..86117c264
--- /dev/null
+++ b/addons/plotting/functions/fnc_onKeyDown.sqf
@@ -0,0 +1,26 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Handles canceling the currently active plot.
+ *
+ * Arguments:
+ * 0: Display or control the EH is attached to (ignored) <DISPLAY or CONTROL>
+ * 1: Key code <NUMBER>
+ *
+ * Return Value:
+ * Handled <BOOL>
+ *
+ * Example:
+ * [_display, 1] call zen_plotting_fnc_onKeyDown
+ *
+ * Public: No
+ */
+
+params ["", "_keyCode"];
+
+if (GVAR(activePlot) isEqualTo [] || {_keyCode != DIK_ESCAPE}) exitWith {false};
+
+TRACE_1("Cancel adding plot",_this);
+GVAR(activePlot) = [];
+
+true
diff --git a/addons/plotting/functions/fnc_onLoad.sqf b/addons/plotting/functions/fnc_onLoad.sqf
new file mode 100644
index 000000000..e041165a8
--- /dev/null
+++ b/addons/plotting/functions/fnc_onLoad.sqf
@@ -0,0 +1,37 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Function triggered every time the Zeus/Curator display is opened.
+ * Adds the draw event handlers to display static and currently active plots in 3D and on the map.
+ *
+ * Arguments:
+ * 0: Zeus Display <DISPLAY>
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [_display] call zen_plotting_fnc_onLoad
+ *
+ * Public: No
+ */
+
+params ["_display"];
+TRACE_1("Zeus display opened",_display);
+
+GVAR(activePlot) = [];
+
+if (!GVAR(draw3DAdded)) then {
+    LOG("Adding Draw3D.");
+    addMissionEventHandler ["Draw3D", {call FUNC(onDraw3D)}];
+    GVAR(draw3DAdded) = true;
+};
+
+LOG("Adding input handlers.");
+_display displayAddEventHandler ["MouseButtonDown", {call FUNC(onMouseButtonDown)}];
+_display displayAddEventHandler ["KeyDown", {call FUNC(onKeyDown)}];
+
+// MapDraw EH needs to be added every time the Zeus display is opened.
+LOG("Adding map draw.");
+private _ctrlMap = _display displayCtrl IDC_RSCDISPLAYCURATOR_MAINMAP;
+_ctrlMap ctrlAddEventHandler ["Draw", {call FUNC(onDraw)}];
diff --git a/addons/plotting/functions/fnc_onMouseButtonDown.sqf b/addons/plotting/functions/fnc_onMouseButtonDown.sqf
new file mode 100644
index 000000000..96d1ed145
--- /dev/null
+++ b/addons/plotting/functions/fnc_onMouseButtonDown.sqf
@@ -0,0 +1,46 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Handles the mouse button event when user wants to add a plot in 3D or on the map.
+ *
+ * Arguments:
+ * 0: Zeus display <DISPLAY>
+ * 1: Mouse button pressed <NUMBER>
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [_display, 1] call zen_plotting_fnc_onMouseButtonDown
+ *
+ * Public: No
+ */
+
+params ["_display", "_button"];
+
+if (_button != 0 || {GVAR(activePlot) isEqualTo []}) exitWith {};
+TRACE_1("onMouseButtonDown",_this);
+
+if (call EFUNC(common,isCursorOnMouseArea)) then {
+    curatorMouseOver params ["_mouseOverType", "_object"];
+
+    private _endPosOrObj = switch (true) do {
+        case (_mouseOverType isEqualTo "OBJECT"): {_object};
+        case (visibleMap): {
+            private _ctrlMap = _display displayCtrl IDC_RSCDISPLAYCURATOR_MAINMAP;
+
+            private _pos2D = _ctrlMap ctrlMapScreenToWorld getMousePosition;
+            _pos2D set [2, getTerrainHeightASL _pos2D];
+            _pos2D
+        };
+        default {[EGVAR(common,mousePos), 2] call EFUNC(common,getPosFromScreen)};
+    };
+
+    GVAR(activePlot) params ["_type", "_startPosOrObj"];
+
+    // Add current active plot to permanent ones
+    TRACE_4("Add plot",_type,_startPosOrObj,_endPosOrObj,_this);
+    [QGVAR(plotAdded), [_type, _startPosOrObj, _endPosOrObj]] call CBA_fnc_localEvent;
+};
+
+GVAR(activePlot) = [];
diff --git a/addons/plotting/functions/fnc_onUnload.sqf b/addons/plotting/functions/fnc_onUnload.sqf
new file mode 100644
index 000000000..e49cba9eb
--- /dev/null
+++ b/addons/plotting/functions/fnc_onUnload.sqf
@@ -0,0 +1,18 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Handles unloading the Zeus Display.
+ *
+ * Arguments:
+ * 0: Display <DISPLAY>
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [DISPLAY] call zen_plotting_fnc_onUnload
+ *
+ * Public: No
+ */
+
+GVAR(activePlot) = [];
diff --git a/addons/plotting/functions/fnc_selectPosition.sqf b/addons/plotting/functions/fnc_selectPosition.sqf
new file mode 100644
index 000000000..8faf08df6
--- /dev/null
+++ b/addons/plotting/functions/fnc_selectPosition.sqf
@@ -0,0 +1,34 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Called from context menu to select the start position of the plot.
+ *
+ * Arguments:
+ * 0: Context menu position ASL <ARRAY>
+ * 1: Selected objects <ARRAY>
+ * 2: Selected groups <ARRAY>
+ * 3: Selected waypoints <ARRAY>
+ * 4: Selected markers <ARRAY>
+ * 5: Hovered entity <OBJECT>
+ * 6: Plot type <STRING>
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * [[100, 100, 5], [], [], [], [], objNull, "LINE"] call zen_plotting_fnc_selectPosition
+ *
+ * Public: No
+ */
+
+params ["_position", "_objects", "_groups", "_waypoints", "_markers", "_hoveredEntity", "_type"];
+
+private _posOrObj = switch (true) do {
+    case (_hoveredEntity isEqualType objNull && {!isNull _hoveredEntity}): {_hoveredEntity};
+    case (count _objects isEqualTo 1): {_objects select 0};
+    default {_position};
+};
+
+TRACE_1("start pos",_posOrObj);
+
+[_type, _posOrObj] call FUNC(setActivePlot);
diff --git a/addons/plotting/functions/fnc_setActivePlot.sqf b/addons/plotting/functions/fnc_setActivePlot.sqf
new file mode 100644
index 000000000..0fc40e2be
--- /dev/null
+++ b/addons/plotting/functions/fnc_setActivePlot.sqf
@@ -0,0 +1,22 @@
+#include "script_component.hpp"
+/*
+ * Authors: Timi007
+ * Sets the currently active plot where the end position is attached to the cursor.
+ *
+ * Arguments:
+ * 0: Type of plot <STRING>
+ * 1: Start position ASL or attached object <ARRAY or OBJECT>
+ *
+ * Return Value:
+ * None
+ *
+ * Example:
+ * ["LINE", curatorCamera] call zen_plotting_fnc_setActivePlot
+ *
+ * Public: No
+ */
+
+params [["_type", "LINE", [""]], ["_startPos", [0, 0, 0], [[], objNull], [3]]];
+
+GVAR(activePlot) = [_type, _startPos];
+
diff --git a/addons/plotting/functions/script_component.hpp b/addons/plotting/functions/script_component.hpp
new file mode 100644
index 000000000..2680773dd
--- /dev/null
+++ b/addons/plotting/functions/script_component.hpp
@@ -0,0 +1 @@
+#include "\x\zen\addons\plotting\script_component.hpp"
diff --git a/addons/plotting/initKeybinds.inc.sqf b/addons/plotting/initKeybinds.inc.sqf
new file mode 100644
index 000000000..8798abd2e
--- /dev/null
+++ b/addons/plotting/initKeybinds.inc.sqf
@@ -0,0 +1,61 @@
+private _category = [ELSTRING(main,DisplayName), LSTRING(DisplayName)];
+
+[
+    _category,
+    QGVAR(toggleDistanceFormat),
+    [LSTRING(ToggleDistanceFormat), LSTRING(ToggleDistanceFormat_Description)],
+    {
+        if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then {
+            private _numFormatters = count GVAR(distanceFormatters);
+            GVAR(currentDistanceFormatter) = (GVAR(currentDistanceFormatter) + 1) % _numFormatters;
+
+            true
+        };
+    },
+    {},
+    [DIK_R, [false, false, false]] // Default: R
+] call CBA_fnc_addKeybind;
+
+[
+    _category,
+    QGVAR(toggleAzimuthFormat),
+    [LSTRING(ToggleAzimuthFormat), LSTRING(ToggleAzimuthFormat_Description)],
+    {
+        if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then {
+            private _numFormatters = count GVAR(azimuthFormatters);
+            GVAR(currentAzimuthFormatter) = (GVAR(currentAzimuthFormatter) + 1) % _numFormatters;
+
+            true
+        };
+    },
+    {},
+    [DIK_TAB, [false, false, false]] // Default: Tab
+] call CBA_fnc_addKeybind;
+
+[
+    _category,
+    QGVAR(deleteLastPlot),
+    [LSTRING(DeleteLastPlot), LSTRING(DeleteLastPlot_Description)],
+    {
+        if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then {
+            if (GVAR(plots) isNotEqualTo []) then {
+                private _lastPlot = GVAR(plots) deleteAt [-1];
+                TRACE_1("Removed last plot",_lastPlot);
+
+                // Automatically delete invalid plots (e.g. when attached object does not exist anymore)
+                while {GVAR(plots) isNotEqualTo []} do {
+                    (GVAR(plots) select -1) params ["", "_startPos", "_endPos"];
+
+                    if ((_startPos isEqualTo objNull) || {_endPos isEqualTo objNull}) then {
+                        _lastPlot = GVAR(plots) deleteAt [-1];
+                        TRACE_1("Automatically removed next invalid plot",_lastPlot);
+                    };
+                };
+            };
+
+            true
+        };
+    },
+    {},
+    [DIK_Y, [false, true, false]] // Default: Ctrl + Y
+] call CBA_fnc_addKeybind;
diff --git a/addons/plotting/initSettings.inc.sqf b/addons/plotting/initSettings.inc.sqf
new file mode 100644
index 000000000..bbb620817
--- /dev/null
+++ b/addons/plotting/initSettings.inc.sqf
@@ -0,0 +1,10 @@
+private _category = [ELSTRING(main,DisplayName), LSTRING(DisplayName)];
+
+[
+    QGVAR(color),
+    "COLOR",
+    localize "str_3den_marker_attribute_color_displayname",
+    _category,
+    [0.9, 0.9, 0, 1],
+    0
+] call CBA_fnc_addSetting;
diff --git a/addons/plotting/script_component.hpp b/addons/plotting/script_component.hpp
new file mode 100644
index 000000000..e9ea4b298
--- /dev/null
+++ b/addons/plotting/script_component.hpp
@@ -0,0 +1,37 @@
+#define COMPONENT plotting
+#define COMPONENT_BEAUTIFIED Plotting
+#include "\x\zen\addons\main\script_mod.hpp"
+
+// #define DEBUG_MODE_FULL
+// #define DISABLE_COMPILE_CACHE
+// #define ENABLE_PERFORMANCE_COUNTERS
+
+#ifdef DEBUG_ENABLED_PLOTTING
+    #define DEBUG_MODE_FULL
+#endif
+
+#ifdef DEBUG_SETTINGS_PLOTTING
+    #define DEBUG_SETTINGS DEBUG_SETTINGS_PLOTTING
+#endif
+
+#include "\x\zen\addons\main\script_macros.hpp"
+
+#include "\a3\ui_f\hpp\defineDIKCodes.inc"
+#include "\x\zen\addons\common\defineResinclDesign.inc"
+
+#define ICON "\a3\ui_f\data\map\markerbrushes\cross_ca.paa"
+#define ICON_ANGLE 0
+#define ICON_SCALE 1
+#define MAP_ICON_SCALE 24
+#define LINEWIDTH 10
+
+#define CIRCLE_EDGES_MIN 12
+#define CIRCLE_RESOLUTION 5
+
+#define CUBOID_HEIGHT_THRESHOLD 0.1
+
+#define MAX_RENDER_DISTANCE 3000
+#define MAX_RENDER_DISTANCE_SQR 9000000
+
+#define CAN_RENDER_ICON(CAMPOS,POS) (CAMPOS vectorDistanceSqr POS <= MAX_RENDER_DISTANCE_SQR)
+#define CAN_RENDER_LINE(CAMPOS,POS1,POS2) ((CAMPOS vectorDistanceSqr POS1 <= MAX_RENDER_DISTANCE_SQR) || {CAMPOS vectorDistanceSqr POS2 <= MAX_RENDER_DISTANCE_SQR} || {[POS1, POS2, MAX_RENDER_DISTANCE, CAMPOS] call FUNC(isPosInCylinder)})
diff --git a/addons/plotting/stringtable.xml b/addons/plotting/stringtable.xml
new file mode 100644
index 000000000..30238068b
--- /dev/null
+++ b/addons/plotting/stringtable.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project name="ZEN">
+    <Package name="Plotting">
+        <Key ID="STR_ZEN_Plotting_DisplayName">
+            <English>Plotting</English>
+            <German>Plotten</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_MeasureDistance">
+            <English>Measure distance</English>
+            <German>Entfernung messen</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_MeasureDistanceFromCamera">
+            <English>Measure distance from camera</English>
+            <German>Entfernung von Kamera messen</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_MeasureRadius">
+            <English>Measure radius</English>
+            <German>Radius messen</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_MeasureOffset">
+            <English>Measure offset</English>
+            <German>Versatz messen</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_ClearPlots">
+            <English>Clear plots</English>
+            <German>Plots entfernen</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_ToggleDistanceFormat">
+            <English>Switch Distance Unit</English>
+            <German>Distanzeinheit durchschalten</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_ToggleDistanceFormat_Description">
+            <English>Switches to the next unit for distances.</English>
+            <German>Wechselt zur nächsten Einheit für Entfernungen.</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_ToggleAzimuthFormat">
+            <English>Switch Azimuth Unit</English>
+            <German>Richtungseinheit durchschalten</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_ToggleAzimuthFormat_Description">
+            <English>Switches to the next unit for directions.</English>
+            <German>Wechselt zur nächsten Einheit für Richtungen.</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_DeleteLastPlot">
+            <English>Delete Last Plot</English>
+            <German>Letzten Plot entfernen</German>
+        </Key>
+        <Key ID="STR_ZEN_Plotting_DeleteLastPlot_Description">
+            <English>Deletes the last plot created.</English>
+            <German>Entfernt den zuletzt erstellten Plot.</German>
+        </Key>
+    </Package>
+</Project>
diff --git a/addons/plotting/ui/cuboid.paa b/addons/plotting/ui/cuboid.paa
new file mode 100644
index 000000000..b1d997f20
Binary files /dev/null and b/addons/plotting/ui/cuboid.paa differ
diff --git a/addons/plotting/ui/radius.paa b/addons/plotting/ui/radius.paa
new file mode 100644
index 000000000..5f6b732b2
Binary files /dev/null and b/addons/plotting/ui/radius.paa differ
diff --git a/addons/plotting/ui/ruler.paa b/addons/plotting/ui/ruler.paa
new file mode 100644
index 000000000..96d03a192
Binary files /dev/null and b/addons/plotting/ui/ruler.paa differ
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 1529f158f..bd303bdca 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -6,6 +6,7 @@
   - [<i class="fas fa-cube"></i> Compositions](/user_guide/compositions.md)
   - [<i class="fas fa-warehouse"></i> Vehicle Garage](/user_guide/garage.md)
   - [<i class="fas fa-map-marked"></i> Area Markers](/user_guide/area_markers.md)
+  - [<i class="fas fa-ruler"></i> Plotting](/user_guide/plotting.md)
 - **Frameworks**
   - [<i class="fas fa-bars"></i> Context Menu](/frameworks/context_menu.md)
   - [<i class="fas fa-cog"></i> Custom Modules](/frameworks/custom_modules.md)
diff --git a/docs/user_guide/context_actions.md b/docs/user_guide/context_actions.md
index 803ad90b1..5cddd642d 100644
--- a/docs/user_guide/context_actions.md
+++ b/docs/user_guide/context_actions.md
@@ -60,6 +60,10 @@ Sub-actions allow for copying and pasting the hovered unit's loadout onto anothe
 Furthermore, the unit's current weapon can be switched between their rifle, handgun, or binoculars.
 The main action acts as a shortcut for the "Edit" sub-action.
 
+## Plotting
+
+Creates a [plot](/user_guide/plotting.md) at the context menu's position or attached object.
+
 ## Remote Control
 
 Starts the remote control process on the hovered unit or vehicle.
diff --git a/docs/user_guide/plotting.md b/docs/user_guide/plotting.md
new file mode 100644
index 000000000..3c1cec898
--- /dev/null
+++ b/docs/user_guide/plotting.md
@@ -0,0 +1,27 @@
+# Plotting
+
+This feature helps you measure distances and directions with 3D and map plots. 
+You can set the starting and ending points to be either fixed locations or attached to an object, allowing the values to update dynamically.
+
+You can cycle between different distance and azimuth formats using the <kbd>R</kbd> and <kbd>Tab &#8633;</kbd> keys, which can be customized in the CBA Keybinding settings. 
+You can also change the plot colors in the CBA settings.
+
+## Measure Distance
+
+This action creates a simple line. It calculates the 3D distance between the starting and ending points and shows it at both ends of the line. 
+The azimuth at the end point indicates the direction from the start point to the end point, while the azimuth at the start point shows the direction from the end point back to the start.
+
+## Measure Radius
+
+This action draws a circle with the starting point at the center. 
+The radius of the circle is determined by the 3D distance between the start and end points. 
+The azimuth at the end point indicates the direction from the center to the end point.
+
+## Measure Offset
+
+This action creates a rectangular cuboid, using the starting point as one corner and the ending point as the opposite corner. 
+The end point displays the offsets in the X, Y, and Z axes relative to the starting point.
+
+## Clear Plots
+
+This action removes all measurement plots from the display.