Settings.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. using ScottPlot.Drawing;
  2. using ScottPlot.Plottable;
  3. using ScottPlot.Renderable;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Collections.ObjectModel;
  7. using System.Collections.Specialized;
  8. using System.Drawing;
  9. using System.Linq;
  10. namespace ScottPlot
  11. {
  12. /// <summary>
  13. /// This module holds state for figure dimensions, axis limits, plot contents, and styling options.
  14. /// A plot can be duplicated by copying the full state of this settings module.
  15. /// </summary>
  16. public class Settings
  17. {
  18. /// <summary>
  19. /// This List contains all plottables managed by this Plot.
  20. /// Render order is from lowest (first) to highest (last).
  21. /// </summary>
  22. public readonly ObservableCollection<IPlottable> Plottables = new();
  23. /// <summary>
  24. /// Unique value that changes any time the list of plottables is modified.
  25. /// </summary>
  26. public int PlottablesIdentifier { get; private set; } = 0;
  27. /// <summary>
  28. /// Return the next color from PlottablePalette based on the current number of plottables
  29. /// </summary>
  30. public Color GetNextColor() => PlottablePalette.GetColor(Plottables.Count);
  31. // renderable objects the user can customize
  32. public readonly FigureBackground FigureBackground = new FigureBackground();
  33. public readonly DataBackground DataBackground = new DataBackground();
  34. public readonly BenchmarkMessage BenchmarkMessage = new BenchmarkMessage();
  35. public readonly ErrorMessage ErrorMessage = new ErrorMessage();
  36. public readonly Legend CornerLegend = new Legend();
  37. public readonly ZoomRectangle ZoomRectangle = new ZoomRectangle();
  38. public (double xMin, double xMax, double yMin, double yMax) ZoomUnitArea { get; private set; }
  39. public Palette PlottablePalette = Palette.Category10;
  40. public Int32 WfmIntensity { get; set; } = 50;
  41. public (Int32 left, Int32 top, Int32 right, Int32 bottom) Margin { get; set; } = (0, 0, 0, 0);
  42. // the Axes list stores styling info for each axis and its limits
  43. public List<Axis> Axes = new List<Axis>() {
  44. new DefaultRightAxis(),//默认的右边刻度
  45. new DefaultLeftAxis(),//默认的左边刻度
  46. new DefaultBottomAxis(),//默认的底边刻度
  47. new DefaultTopAxis()//默认的上边刻度
  48. };
  49. public int[] XAxisIndexes => Axes.Where(x => x.IsHorizontal).Select(x => x.AxisIndex).Distinct().ToArray();
  50. public int[] YAxisIndexes => Axes.Where(x => x.IsVertical).Select(x => x.AxisIndex).Distinct().ToArray();
  51. public Axis GetXAxis(int xAxisIndex) => Axes.Where(x => x.IsHorizontal && x.AxisIndex == xAxisIndex).First();
  52. public Axis GetYAxis(int yAxisIndex) => Axes.Where(x => x.IsVertical && x.AxisIndex == yAxisIndex).First();
  53. public bool AllAxesHaveBeenSet => Axes.All(x => x.Dims.HasBeenSet);
  54. public EqualScaleMode EqualScaleMode = EqualScaleMode.Disabled;
  55. // shortcuts to fixed axes indexes
  56. public Axis YAxis => Axes[0];//默认Y轴
  57. public Axis YAxis2 => Axes[1];
  58. public Axis XAxis => Axes[2];//默认X轴
  59. public Axis XAxis2 => Axes[3];
  60. // public fields represent primary X and Y axes
  61. public int Width => (int)XAxis.Dims.FigureSizePx;
  62. public int Height => (int)YAxis.Dims.FigureSizePx;
  63. public float DataOffsetX => XAxis.Dims.DataOffsetPx;
  64. public float DataOffsetY => YAxis.Dims.DataOffsetPx;
  65. public float DataWidth => XAxis.Dims.DataSizePx;
  66. public float DataHeight => YAxis.Dims.DataSizePx;
  67. public Settings()
  68. {
  69. Plottables.CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) => PlottablesIdentifier++;
  70. }
  71. /// <summary>
  72. /// Return figure dimensions for the specified X and Y axes
  73. /// </summary>
  74. public PlotDimensions GetPlotDimensions(int xAxisIndex, int yAxisIndex, double scaleFactor)
  75. {
  76. var xAxis = GetXAxis(xAxisIndex);
  77. var yAxis = GetYAxis(yAxisIndex);
  78. // determine figure dimensions based on primary X and Y axis
  79. var figureSize = new SizeF(XAxis.Dims.FigureSizePx, YAxis.Dims.FigureSizePx);//整个图片大小
  80. var dataSize = new SizeF(XAxis.Dims.DataSizePx, YAxis.Dims.DataSizePx);//波形绘制区域大小
  81. var dataOffset = new PointF(XAxis.Dims.DataOffsetPx, YAxis.Dims.DataOffsetPx);//左上角的偏移量
  82. // determine axis limits based on specific X and Y axes
  83. (double xMin, double xMax) = xAxis.Dims.RationalLimits();
  84. (double yMin, double yMax) = yAxis.Dims.RationalLimits();
  85. var limits = (xMin, xMax, yMin, yMax);
  86. return new PlotDimensions(figureSize, dataSize, dataOffset, limits, scaleFactor,(xAxis.GetMinorLogScale(), yAxis.GetMinorLogScale()));
  87. }
  88. /// <summary>
  89. /// Set the default size for rendering images
  90. /// </summary>
  91. public void Resize(float width, float height)
  92. {
  93. foreach (Axis axis in Axes)//设置所有坐标轴的大小
  94. axis.Dims.Resize(axis.IsHorizontal ? width : height);
  95. }
  96. /// <summary>
  97. /// 更新通道的垂直标记
  98. /// </summary>
  99. /// <param name="position"></param>
  100. /// <param name="scale"></param>
  101. /// <param name="unitPrefix"></param>
  102. /// <param name="scaleUnit"></param>
  103. /// <param name="color"></param>
  104. /// <param name="interval"></param>
  105. /// <param name="isUnitPrefix"></param>
  106. /// <param name="tickDecimalPlaces"></param>
  107. public void ResetChannelParameter(Int32 position, double scale, string unitPrefix, string scaleUnit, Color color ,Int32 interval = 1000,Boolean isUnitPrefix=true, Int32 tickDecimalPlaces = 4)
  108. {
  109. YAxis.Dims.Update(position, scale, unitPrefix, scaleUnit, interval, isUnitPrefix, tickDecimalPlaces);
  110. YAxis.Dims.SetTickLabelColor(color);
  111. }
  112. /// <summary>
  113. /// 更新水平标记
  114. /// </summary>
  115. /// <param name="position"></param>
  116. /// <param name="scale"></param>
  117. /// <param name="unitPrefix"></param>
  118. /// <param name="scaleUnit"></param>
  119. /// <param name="color"></param>
  120. /// <param name="interval"></param>
  121. /// <param name="isUnitPrefix"></param>
  122. /// <param name="tickDecimalPlaces"></param>
  123. public void ResetTimebaseParameter(Int32 position, double scale, string unitPrefix, string scaleUnit, Color color, Int32 interval = 1000, Boolean isUnitPrefix = true, Int32 tickDecimalPlaces = 4)
  124. {
  125. XAxis.Dims.Update(position, scale, unitPrefix, scaleUnit, interval, isUnitPrefix, tickDecimalPlaces);
  126. XAxis.Dims.SetTickLabelColor(color);
  127. }
  128. /// <summary>
  129. /// Reset axis limits to their defauts
  130. /// </summary>
  131. public void ResetAxisLimits()
  132. {
  133. //foreach (Axis axis in Axes)
  134. // axis.Dims.ResetLimits();
  135. }
  136. /// <summary>
  137. /// Define axis limits for a particuar axis
  138. /// </summary>
  139. public void AxisSet(double? xMin, double? xMax, double? yMin, double? yMax, int xAxisIndex = 0, int yAxisIndex = 0)
  140. {
  141. GetXAxis(xAxisIndex).Dims.SetAxis(xMin, xMax);
  142. GetYAxis(yAxisIndex).Dims.SetAxis(yMin, yMax);
  143. }
  144. /// <summary>
  145. /// Define axis limits for a particuar axis
  146. /// </summary>
  147. public void AxisSet(AxisLimits limits, int xAxisIndex = 0, int yAxisIndex = 0)
  148. {
  149. GetXAxis(xAxisIndex).Dims.SetAxis(limits.XMin, limits.XMax);
  150. GetYAxis(yAxisIndex).Dims.SetAxis(limits.YMin, limits.YMax);
  151. }
  152. /// <summary>
  153. /// Return X and Y axis limits
  154. /// </summary>
  155. public AxisLimits AxisLimits(int xAxisIndex, int yAxisIndex)
  156. {
  157. var xAxis = GetXAxis(xAxisIndex);
  158. var yAxis = GetYAxis(yAxisIndex);
  159. return new AxisLimits(xAxis.Dims.Min, xAxis.Dims.Max, yAxis.Dims.Min, yAxis.Dims.Max);
  160. }
  161. /// <summary>
  162. /// Pan all axes by the given pixel distance
  163. /// </summary>
  164. public void AxesPanPx(float dxPx, float dyPx)
  165. {
  166. //List<int> modifiedXs = new List<int>();
  167. //foreach (Axis axis in Axes.Where(x => x.IsHorizontal))
  168. //{
  169. // if (!modifiedXs.Contains(axis.AxisIndex))
  170. // {
  171. // axis.Dims.PanPx(axis.IsHorizontal ? dxPx : dyPx);
  172. // modifiedXs.Add(axis.AxisIndex);
  173. // }
  174. //}
  175. //List<int> modifiedYs = new List<int>();
  176. //foreach (Axis axis in Axes.Where(x => x.IsVertical))
  177. //{
  178. // if (!modifiedYs.Contains(axis.AxisIndex))
  179. // {
  180. // axis.Dims.PanPx(axis.IsHorizontal ? dxPx : dyPx);
  181. // modifiedYs.Add(axis.AxisIndex);
  182. // }
  183. //}
  184. }
  185. /// <summary>
  186. /// Zoom all axes by the given pixel distance
  187. /// </summary>
  188. public void AxesZoomPx(float xPx, float yPx, bool lockRatio = false)
  189. {
  190. //if (lockRatio)
  191. // (xPx, yPx) = (Math.Max(xPx, yPx), Math.Max(xPx, yPx));
  192. //foreach (Axis axis in Axes)
  193. //{
  194. // double deltaPx = axis.IsHorizontal ? xPx : yPx;
  195. // double delta = deltaPx * axis.Dims.UnitsPerPx;
  196. // double deltaFrac = delta / (Math.Abs(delta) + axis.Dims.Span);
  197. // axis.Dims.Zoom(Math.Pow(10, deltaFrac));
  198. //}
  199. }
  200. /// <summary>
  201. /// Zoom all axes by the given fraction
  202. /// </summary>
  203. public void AxesZoomTo(double xFrac, double yFrac, float xPixel, float yPixel)
  204. {
  205. //foreach (Axis axis in Axes)
  206. //{
  207. // double frac = axis.IsHorizontal ? xFrac : yFrac;
  208. // float centerPixel = axis.IsHorizontal ? xPixel : yPixel;
  209. // double center = axis.Dims.GetUnit(centerPixel);
  210. // axis.Dims.Zoom(frac, center);
  211. //}
  212. }
  213. /// <summary>
  214. /// Automatically adjust X and Y axis limits of all axes to fit the data
  215. /// </summary>
  216. public void AxisAutoAll(double horizontalMargin = .1, double verticalMargin = .1)
  217. {
  218. //AxisAutoAllX(horizontalMargin);
  219. //AxisAutoAllY(verticalMargin);
  220. }
  221. /// <summary>
  222. /// Automatically adjust axis limits for all axes which have not yet been set
  223. /// </summary>
  224. public void AxisAutoUnsetAxes()
  225. {
  226. /* Extra logic here ensures axes index only get auto-set once
  227. * in the case that multiple axes share the same axis index
  228. */
  229. //var xAxes = Axes.Where(x => x.IsHorizontal);
  230. //var yAxes = Axes.Where(x => x.IsVertical);
  231. //var setIndexesX = xAxes.Where(x => x.Dims.HasBeenSet && !x.Dims.IsNan).Select(x => x.AxisIndex).Distinct().ToList();
  232. //var setIndexesY = yAxes.Where(x => x.Dims.HasBeenSet && !x.Dims.IsNan).Select(x => x.AxisIndex).Distinct().ToList();
  233. //foreach (Axis axis in xAxes)
  234. //{
  235. // if (axis.Dims.HasBeenSet && !axis.Dims.IsNan)
  236. // continue;
  237. // if (setIndexesX.Contains(axis.AxisIndex))
  238. // continue;
  239. // setIndexesX.Add(axis.AxisIndex);
  240. // AxisAutoX(axis.AxisIndex);
  241. //}
  242. //foreach (Axis axis in yAxes)
  243. //{
  244. // if (axis.Dims.HasBeenSet && !axis.Dims.IsNan)
  245. // continue;
  246. // if (setIndexesY.Contains(axis.AxisIndex))
  247. // continue;
  248. // setIndexesY.Add(axis.AxisIndex);
  249. // AxisAutoY(axis.AxisIndex);
  250. //}
  251. }
  252. /// <summary>
  253. /// If a scale lock mode is in use, modify the axis limits accordingly
  254. /// </summary>
  255. public void EnforceEqualAxisScales()
  256. {
  257. switch (EqualScaleMode)
  258. {
  259. case EqualScaleMode.Disabled:
  260. return;
  261. case EqualScaleMode.PreserveX:
  262. double yHalfSize = (YAxis.Dims.DataSizePx / 2) * XAxis.Dims.UnitsPerPx;
  263. AxisSet(null, null, YAxis.Dims.Center - yHalfSize, YAxis.Dims.Center + yHalfSize);
  264. return;
  265. case EqualScaleMode.PreserveY:
  266. double xHalfSize = (XAxis.Dims.DataSizePx / 2) * YAxis.Dims.UnitsPerPx;
  267. AxisSet(XAxis.Dims.Center - xHalfSize, XAxis.Dims.Center + xHalfSize, null, null);
  268. return;
  269. case EqualScaleMode.ZoomOut:
  270. double maxUnitsPerPx = Math.Max(XAxis.Dims.UnitsPerPx, YAxis.Dims.UnitsPerPx);
  271. double halfX = (XAxis.Dims.DataSizePx / 2) * maxUnitsPerPx;
  272. double halfY = (YAxis.Dims.DataSizePx / 2) * maxUnitsPerPx;
  273. AxisSet(XAxis.Dims.Center - halfX, XAxis.Dims.Center + halfX, YAxis.Dims.Center - halfY, YAxis.Dims.Center + halfY);
  274. return;
  275. default:
  276. throw new InvalidOperationException("unknown scale lock mode");
  277. }
  278. }
  279. /// <summary>
  280. /// Automatically adjust X axis limits to fit the data
  281. /// </summary>
  282. public void AxisAutoAllX(double margin = .1)
  283. {
  284. //int[] xAxisIndexes = Axes.Where(x => x.IsHorizontal).Select(x => x.AxisIndex).Distinct().ToArray();
  285. //foreach (int i in xAxisIndexes)
  286. // AxisAutoX(i, margin);
  287. }
  288. /// <summary>
  289. /// Automatically adjust Y axis limits to fit the data
  290. /// </summary>
  291. public void AxisAutoAllY(double margin = .1)
  292. {
  293. //int[] yAxisIndexes = Axes.Where(x => x.IsVertical).Select(x => x.AxisIndex).Distinct().ToArray();
  294. //foreach (int i in yAxisIndexes)
  295. // AxisAutoY(i, margin);
  296. }
  297. private void AxisAutoX(int xAxisIndex, double margin = .1)
  298. {
  299. double min = double.NaN;
  300. double max = double.NaN;
  301. double zoomFrac = 1 - margin;
  302. var plottableLimits = Plottables.Where(x => x is IPlottable)
  303. .Select(x => (IPlottable)x)
  304. .Where(x => x.IsVisible)
  305. .Where(x => x.XAxisIndex == xAxisIndex)
  306. .Select(x => x.GetAxisLimits())
  307. .ToArray();
  308. foreach (var limits in plottableLimits)
  309. {
  310. if (!double.IsNaN(limits.XMin))
  311. min = double.IsNaN(min) ? limits.XMin : Math.Min(min, limits.XMin);
  312. if (!double.IsNaN(limits.XMax))
  313. max = double.IsNaN(max) ? limits.XMax : Math.Max(max, limits.XMax);
  314. }
  315. if (double.IsNaN(min) && double.IsNaN(max))
  316. return;
  317. var xAxis = GetXAxis(xAxisIndex);
  318. xAxis.Dims.SetAxis(min, max);
  319. xAxis.Dims.Zoom(zoomFrac);
  320. }
  321. private void AxisAutoY(int yAxisIndex, double margin = .1)
  322. {
  323. double min = double.NaN;
  324. double max = double.NaN;
  325. double zoomFrac = 1 - margin;
  326. var plottableLimits = Plottables.Where(x => x is IPlottable)
  327. .Select(x => (IPlottable)x)
  328. .Where(x => x.IsVisible)
  329. .Where(x => x.YAxisIndex == yAxisIndex)
  330. .Select(x => x.GetAxisLimits())
  331. .ToArray();
  332. foreach (var limits in plottableLimits)
  333. {
  334. if (!double.IsNaN(limits.YMin))
  335. min = double.IsNaN(min) ? limits.YMin : Math.Min(min, limits.YMin);
  336. if (!double.IsNaN(limits.YMax))
  337. max = double.IsNaN(max) ? limits.YMax : Math.Max(max, limits.YMax);
  338. }
  339. if (double.IsNaN(min) && double.IsNaN(max))
  340. return;
  341. var yAxis = GetYAxis(yAxisIndex);
  342. yAxis.Dims.SetAxis(min, max);
  343. yAxis.Dims.Zoom(zoomFrac);
  344. }
  345. /// <summary>
  346. /// Store axis limits (useful for storing state upon a MouseDown event)
  347. /// </summary>
  348. public void RememberAxisLimits()
  349. {
  350. AxisAutoUnsetAxes();
  351. foreach (Axis axis in Axes)
  352. axis.Dims.Remember();
  353. }
  354. /// <summary>
  355. /// Recall axis limits (useful for recalling state from a previous MouseDown event)
  356. /// </summary>
  357. public void RecallAxisLimits()
  358. {
  359. foreach (Axis axis in Axes)
  360. axis.Dims.Recall();
  361. }
  362. public float MouseDownX { get; private set; }
  363. public float MouseDownY { get; private set; }
  364. /// <summary>
  365. /// Remember mouse position (do this before calling MousePan or MouseZoom)
  366. /// </summary>
  367. public void MouseDown(float mouseDownX, float mouseDownY)
  368. {
  369. RememberAxisLimits();
  370. MouseDownX = mouseDownX;
  371. MouseDownY = mouseDownY;
  372. }
  373. /// <summary>
  374. /// Pan all axes based on the mouse position now vs that last given to MouseDown()
  375. /// </summary>
  376. public void MousePan(float mouseNowX, float mouseNowY)
  377. {
  378. RecallAxisLimits();
  379. AxesPanPx(MouseDownX - mouseNowX, mouseNowY - MouseDownY);
  380. }
  381. /// <summary>
  382. /// Zoom all axes based on the mouse position now vs that last given to MouseDown()
  383. /// </summary>
  384. public void MouseZoom(float mouseNowX, float mouseNowY)
  385. {
  386. RecallAxisLimits();
  387. AxesZoomPx(mouseNowX - MouseDownX, MouseDownY - mouseNowY);
  388. }
  389. public void MouseZoomRect(float mouseNowX, float mouseNowY, bool finalize = false)
  390. {
  391. float left = Math.Min(MouseDownX, mouseNowX);
  392. float right = Math.Max(MouseDownX, mouseNowX);
  393. float top = Math.Min(MouseDownY, mouseNowY);
  394. float bottom = Math.Max(MouseDownY, mouseNowY);
  395. float width = right - left;
  396. float height = bottom - top;
  397. if (finalize)
  398. {
  399. double x1 = XAxis.Dims.GetUnit(left);
  400. double x2 = XAxis.Dims.GetUnit(right);
  401. double y1 = YAxis.Dims.GetUnit(bottom);
  402. double y2 = YAxis.Dims.GetUnit(top);
  403. ZoomRectangle.Clear();
  404. //AxisSet(x1, x2, y1, y2);
  405. //更新zoom区域
  406. ZoomUnitArea = (x1, x2, y1, y2);
  407. }
  408. else
  409. {
  410. // TODO: dont require data offset shifting prior to calling this
  411. ZoomRectangle.Set(left - DataOffsetX, top - DataOffsetY, width, height);
  412. }
  413. }
  414. /// <summary>
  415. /// 清除预览的ZoomRectangle
  416. /// </summary>
  417. public void ClearZoomRectangle()
  418. {
  419. ZoomRectangle.Clear();
  420. }
  421. /// <summary>
  422. /// 获取zoom区域
  423. /// </summary>
  424. /// <param name="mouseNowX"></param>
  425. /// <param name="mouseNowY"></param>
  426. /// <returns></returns>
  427. [Obsolete]
  428. public (double xMin, double xMax, double yMin, double yMax) GetMouseZoomRect(float mouseNowX, float mouseNowY)
  429. {
  430. float left = Math.Min(MouseDownX, mouseNowX);
  431. float right = Math.Max(MouseDownX, mouseNowX);
  432. float top = Math.Min(MouseDownY, mouseNowY);
  433. float bottom = Math.Max(MouseDownY, mouseNowY);
  434. float width = right - left;
  435. float height = bottom - top;
  436. double x1 = XAxis.Dims.GetUnit(left);
  437. double x2 = XAxis.Dims.GetUnit(right);
  438. double y1 = YAxis.Dims.GetUnit(bottom);
  439. double y2 = YAxis.Dims.GetUnit(top);
  440. return (x1, x2, y1, y2);
  441. }
  442. /// <summary>
  443. /// Ensure all axes have the same size and offset as the primary X and Y axis
  444. /// </summary>
  445. public void CopyPrimaryLayoutToAllAxes()
  446. {
  447. foreach (Axis axis in Axes)
  448. {
  449. if (axis.IsHorizontal)
  450. axis.Dims.Resize(Width, DataWidth, DataOffsetX);
  451. else
  452. axis.Dims.Resize(Height, DataHeight, DataOffsetY);
  453. }
  454. }
  455. public void LayoutAuto()
  456. {
  457. foreach (int xAxisIndex in XAxisIndexes)
  458. LayoutAuto(xAxisIndex, 0);
  459. foreach (int yAxisIndex in YAxisIndexes)
  460. LayoutAuto(0, yAxisIndex);
  461. }
  462. private void LayoutAuto(int xAxisIndex, int yAxisIndex)
  463. {
  464. // TODO: separate this into distinct X and Y functions (requires refactoring plottable interface)
  465. bool atLeastOneAxisIsZero = xAxisIndex == 0 || yAxisIndex == 0;
  466. if (!atLeastOneAxisIsZero)
  467. throw new InvalidOperationException();
  468. // Adjust padding around the data area to accommodate title and tick labels.
  469. //
  470. // This is a chicken-and-egg problem:
  471. // * TICK DENSITY depends on the DATA AREA SIZE
  472. // * DATA AREA SIZE depends on LAYOUT PADDING
  473. // * LAYOUT PADDING depends on MAXIMUM LABEL SIZE
  474. // * MAXIMUM LABEL SIZE depends on TICK DENSITY
  475. //
  476. // To solve this, start by assuming data area size == figure size and layout padding == 0,
  477. // then calculate ticks, then set padding based on the largest tick, then re-calculate ticks.
  478. // axis limits shall not change
  479. var dims = GetPlotDimensions(xAxisIndex, yAxisIndex, scaleFactor: 1.0);
  480. var limits = (dims.XMin, dims.XMax, dims.YMin, dims.YMax);
  481. var figSize = new SizeF(Width, Height);
  482. // first-pass tick calculation based on full image size
  483. var dimsFull = new PlotDimensions(figSize, figSize, new PointF(0, 0), limits, scaleFactor: 1,(dims.XMinorTickDistribution, dims.YMinorTickDistribution));
  484. foreach (var axis in Axes)
  485. {
  486. bool isMatchingXAxis = axis.IsHorizontal && axis.AxisIndex == xAxisIndex;
  487. bool isMatchingYAxis = axis.IsVertical && axis.AxisIndex == yAxisIndex;
  488. if (isMatchingXAxis || isMatchingYAxis)
  489. {
  490. axis.RecalculateTickPositions(dimsFull);
  491. axis.RecalculateAxisSize();
  492. }
  493. }
  494. // now adjust our layout based on measured axis sizes
  495. RecalculateDataPadding();
  496. // now recalculate ticks based on new layout
  497. var dataSize = new SizeF(DataWidth, DataHeight);
  498. var dataOffset = new PointF(DataOffsetX, DataOffsetY);
  499. var dims3 = new PlotDimensions(figSize, dataSize, dataOffset, limits, scaleFactor: 1.0, (dims.XMinorTickDistribution, dims.YMinorTickDistribution));
  500. foreach (var axis in Axes)
  501. {
  502. bool isMatchingXAxis = axis.IsHorizontal && axis.AxisIndex == xAxisIndex;
  503. bool isMatchingYAxis = axis.IsVertical && axis.AxisIndex == yAxisIndex;
  504. if (isMatchingXAxis || isMatchingYAxis)
  505. {
  506. axis.RecalculateTickPositions(dims3);
  507. }
  508. }
  509. // adjust the layout based on measured tick label sizes
  510. RecalculateDataPadding();
  511. }
  512. private void RecalculateDataPadding()
  513. {
  514. Edge[] edges = { Edge.Left, Edge.Right, Edge.Top, Edge.Bottom };
  515. foreach (var edge in edges)
  516. {
  517. float offset = 0;
  518. foreach (var axis in Axes.Where(x => x.Edge == edge))
  519. {
  520. axis.SetOffset(offset);
  521. offset += axis.GetSize();
  522. }
  523. }
  524. //float padLeft = Axes.Where(x => x.Edge == Edge.Left).Select(x => x.GetSize()).Sum();
  525. //float padRight = Axes.Where(x => x.Edge == Edge.Right).Select(x => x.GetSize()).Sum();
  526. //float padBottom = Axes.Where(x => x.Edge == Edge.Bottom).Select(x => x.GetSize()).Sum();
  527. //float padTop = Axes.Where(x => x.Edge == Edge.Top).Select(x => x.GetSize()).Sum();
  528. //屏蔽多坐标带来的边界计算
  529. float padLeft = Margin.left;
  530. float padRight = Margin.right;
  531. float padBottom = Margin.bottom;
  532. float padTop = Margin.top;
  533. foreach (Axis axis in Axes)
  534. {
  535. if (axis.IsHorizontal)
  536. axis.Dims.SetPadding(padLeft, padRight);//设置上下左右宽度
  537. else
  538. axis.Dims.SetPadding(padTop, padBottom);
  539. }
  540. }
  541. }
  542. }