diff --git a/src/OSPSuite.Assets/UIConstants.cs b/src/OSPSuite.Assets/UIConstants.cs index e704cd105..542363631 100644 --- a/src/OSPSuite.Assets/UIConstants.cs +++ b/src/OSPSuite.Assets/UIConstants.cs @@ -84,6 +84,8 @@ public static class Captions public static readonly string Scaling = "Scaling"; public static readonly string AxisMinimum = "Axis Minimum"; public static readonly string AxisMaximum = "Axis Maximum"; + public static readonly string AxisMajorInterval = "Major Interval"; + public static readonly string AxisMinorTicks = "Minor Ticks"; public static readonly string GridLines = "Grid Lines"; public static readonly string DefaultColor = "Default Color"; public static readonly string DefaultLineStyle = "Default Line Style"; @@ -1274,6 +1276,8 @@ public static class AxisOptions public static string GridLines = "Grid"; public static string Min = "Min"; public static string Max = "Max"; + public static string MajorInterval = "Major Interval"; + public static string MinorTicks = "Minor Ticks"; public static string Caption = "Caption"; public static string DefaultLinestyle = "Default Linestyle"; public static string DefaultColor = "Default Color"; @@ -1988,6 +1992,10 @@ public static string AxisMinMustBeLessThanOrEqualToAxisMax(float? axisMaximumVal } public static readonly string LogAxisMaxMustBeGreaterThanZero = "Loagarithmic axis maximum must be greater than 0"; + + public static readonly string AxisMajorIntervalMustBeGreaterThanZero = "The axis major interval must be greater than 0"; + + public static readonly string AxisMinorTicksMustBeGreaterThanZeroAndLessThan100 = "The number of minor ticks must be greater than 0 and less than 100"; } public static class Rules diff --git a/src/OSPSuite.Core/Chart/Axis.cs b/src/OSPSuite.Core/Chart/Axis.cs index 196b7e8f9..6da99cb98 100644 --- a/src/OSPSuite.Core/Chart/Axis.cs +++ b/src/OSPSuite.Core/Chart/Axis.cs @@ -26,10 +26,16 @@ public enum NumberModes public class Axis : Notifier, IWithDimension, IValidatable { + // DevExpress only accepts a minor tick count that is greater than 0 and less than 100. + public const int MIN_MINOR_TICKS = 1; + public const int MAX_MINOR_TICKS = 99; + private IDimension _dimension; private bool _gridLines; private float? _max; private float? _min; + private float? _majorInterval; + private int? _minorTicks; private NumberModes _numberMode; private Scalings _scaling; private string _unitName; @@ -56,6 +62,8 @@ public Axis(AxisTypes axisType) _gridLines = false; _min = null; _max = null; + _majorInterval = null; + _minorTicks = null; Visible = true; _defaultLineStyle = defaultLineStyleForAxisType(); _defaultColor = Color.White; @@ -69,11 +77,15 @@ internal static IEnumerable AllRules() yield return maxGreaterThanOrEqualToMin; yield return minLessThanOrEqualToMax; yield return maxGreaterThanZero; + yield return majorIntervalGreaterThanZero; + yield return minorTicksWithinValidRange; } private static IBusinessRule maxGreaterThanOrEqualToMin { get; } = createMaxGreaterThanOrEqualToMinRuleForAxis(); private static IBusinessRule minLessThanOrEqualToMax { get; } = createMinLessThanOrEqualToMaxRuleForAxis(); private static IBusinessRule maxGreaterThanZero { get; } = createMaxGreaterThanZeroRuleForLogarithmicAxis(); + private static IBusinessRule majorIntervalGreaterThanZero { get; } = createMajorIntervalGreaterThanZeroRuleForAxis(); + private static IBusinessRule minorTicksWithinValidRange { get; } = createMinorTicksWithinValidRangeRuleForAxis(); private static IBusinessRule createMinLessThanOrEqualToMaxRuleForAxis() => CreateRule.For() .Property(axis => axis.Min) @@ -89,6 +101,17 @@ private static IBusinessRule createMaxGreaterThanOrEqualToMinRuleForAxis() => Cr .Property(axis => axis.Max) .WithRule((axis, value) => !value.HasValue || !axis.Min.HasValue || value >= axis.Min) .WithError((axis, value) => Validation.AxisMaxMustBeGreaterThanOrEqualToAxisMin(axis.Min)); + + private static IBusinessRule createMajorIntervalGreaterThanZeroRuleForAxis() => CreateRule.For() + .Property(axis => axis.MajorInterval) + .WithRule((axis, value) => !value.HasValue || value > 0F) + .WithError((axis, value) => Validation.AxisMajorIntervalMustBeGreaterThanZero); + + // The allowed range is dictated by DevExpress: its axis rejects a minor tick count outside [MIN_MINOR_TICKS, MAX_MINOR_TICKS]. + private static IBusinessRule createMinorTicksWithinValidRangeRuleForAxis() => CreateRule.For() + .Property(axis => axis.MinorTicks) + .WithRule((axis, value) => !value.HasValue || (value >= MIN_MINOR_TICKS && value <= MAX_MINOR_TICKS)) + .WithError((axis, value) => Validation.AxisMinorTicksMustBeGreaterThanZeroAndLessThan100); } private LineStyles defaultLineStyleForAxisType() @@ -123,6 +146,8 @@ public void UpdateFrom(Axis axis) UnitName = axis.UnitName; GridLines = axis.GridLines; SetRange(axis.Min, axis.Max); + MajorInterval = axis.MajorInterval; + MinorTicks = axis.MinorTicks; DefaultLineStyle = axis.DefaultLineStyle; DefaultColor = axis.DefaultColor; Visible = axis.Visible; @@ -229,6 +254,26 @@ public float? Max set => SetProperty(ref _max, value); } + /// + /// Spacing between major gridlines/ticks in axis units. When null, the spacing is calculated automatically. + /// Only applied for linearly scaled axes. + /// + public float? MajorInterval + { + get => _majorInterval; + set => SetProperty(ref _majorInterval, value); + } + + /// + /// Number of minor ticks between two adjacent major ticks. When null, the count is calculated automatically. + /// Only applied for linearly scaled axes. + /// + public int? MinorTicks + { + get => _minorTicks; + set => SetProperty(ref _minorTicks, value); + } + public Unit Unit => _dimension?.Unit(UnitName); /// diff --git a/src/OSPSuite.Core/Serialization/Chart/AxisXmlSerializer.cs b/src/OSPSuite.Core/Serialization/Chart/AxisXmlSerializer.cs index bb9208a3b..205176a4b 100644 --- a/src/OSPSuite.Core/Serialization/Chart/AxisXmlSerializer.cs +++ b/src/OSPSuite.Core/Serialization/Chart/AxisXmlSerializer.cs @@ -19,6 +19,8 @@ public override void PerformMapping() Map(x => x.UnitName).WithMappingName(Constants.Serialization.Attribute.UNIT_NAME); Map(x => x.Min); Map(x => x.Max); + Map(x => x.MajorInterval); + Map(x => x.MinorTicks); Map(x => x.Scaling); Map(x => x.NumberMode); Map(x => x.GridLines); diff --git a/src/OSPSuite.Core/Snapshots/Axis.cs b/src/OSPSuite.Core/Snapshots/Axis.cs index c59a240b7..7c0248f10 100644 --- a/src/OSPSuite.Core/Snapshots/Axis.cs +++ b/src/OSPSuite.Core/Snapshots/Axis.cs @@ -14,6 +14,8 @@ public class Axis public bool Visible { get; set; } public float? Min { get; set; } public float? Max { get; set; } + public float? MajorInterval { get; set; } + public int? MinorTicks { get; set; } public Color DefaultColor { get; set; } public LineStyles DefaultLineStyle { get; set; } public Scalings Scaling { get; set; } diff --git a/src/OSPSuite.Core/Snapshots/Mappers/AxisMapper.cs b/src/OSPSuite.Core/Snapshots/Mappers/AxisMapper.cs index 4a94e5fe6..80d82ba9a 100644 --- a/src/OSPSuite.Core/Snapshots/Mappers/AxisMapper.cs +++ b/src/OSPSuite.Core/Snapshots/Mappers/AxisMapper.cs @@ -28,6 +28,8 @@ public override Task MapToSnapshot(Core.Chart.Axis axis) x.DefaultLineStyle = axis.DefaultLineStyle; x.Min = axis.Min; x.Max = axis.Max; + x.MajorInterval = axis.MajorInterval; + x.MinorTicks = axis.MinorTicks; }); } @@ -49,6 +51,8 @@ public override Task MapToSnapshot(Core.Chart.Axis axis) axis.UnitName = ModelValueFor(snapshot.Unit); axis.Min = snapshot.Min; axis.Max = snapshot.Max; + axis.MajorInterval = snapshot.MajorInterval; + axis.MinorTicks = snapshot.MinorTicks; return Task.FromResult(axis); } diff --git a/src/OSPSuite.Presentation/Presenters/Charts/AxisSettingsPresenter.cs b/src/OSPSuite.Presentation/Presenters/Charts/AxisSettingsPresenter.cs index 93cb92696..4d0da67f8 100644 --- a/src/OSPSuite.Presentation/Presenters/Charts/AxisSettingsPresenter.cs +++ b/src/OSPSuite.Presentation/Presenters/Charts/AxisSettingsPresenter.cs @@ -18,6 +18,8 @@ public enum AxisOptionsColumns GridLines, Min, Max, + MajorInterval, + MinorTicks, Caption, DefaultLineStyle, DefaultColor @@ -73,6 +75,8 @@ protected override void SetDefaultColumnSettings() AddColumnSettings(AxisOptionsColumns.NumberMode).WithCaption(Captions.Chart.AxisOptions.NumberMode); AddColumnSettings(AxisOptionsColumns.Min).WithCaption(Captions.Chart.AxisOptions.Min); AddColumnSettings(AxisOptionsColumns.Max).WithCaption(Captions.Chart.AxisOptions.Max); + AddColumnSettings(AxisOptionsColumns.MajorInterval).WithCaption(Captions.Chart.AxisOptions.MajorInterval); + AddColumnSettings(AxisOptionsColumns.MinorTicks).WithCaption(Captions.Chart.AxisOptions.MinorTicks); AddColumnSettings(AxisOptionsColumns.GridLines).WithCaption(Captions.Chart.AxisOptions.GridLines); AddColumnSettings(AxisOptionsColumns.DefaultLineStyle).WithCaption(Captions.Chart.AxisOptions.DefaultLinestyle); AddColumnSettings(AxisOptionsColumns.DefaultColor).WithCaption(Captions.Chart.AxisOptions.DefaultColor); @@ -81,6 +85,8 @@ protected override void SetDefaultColumnSettings() public void UnitChanged(Axis axis) { axis.ResetRange(); + // the major interval is expressed in axis units, so it is no longer meaningful once the unit changes + axis.MajorInterval = null; } public void NotifyAxisPropertyChanged(Axis axis) => AxisPropertyChanged(this, new AxisEventArgs(axis)); diff --git a/src/OSPSuite.UI/Binders/AxisBinder.cs b/src/OSPSuite.UI/Binders/AxisBinder.cs index 22378b8ca..09bfbb392 100644 --- a/src/OSPSuite.UI/Binders/AxisBinder.cs +++ b/src/OSPSuite.UI/Binders/AxisBinder.cs @@ -212,8 +212,50 @@ private void automaticallyConfigureAxisScale() _axisView.NumericScaleOptions.AutoGrid = true; } + // The manual tick override is only applied for linearly scaled axes. Logarithmic axes keep their + // decade gridlines and the fixed minor count handled in setAxisRange. + private bool hasManualTickSettings => Axis.Scaling != Scalings.Log && (hasValidMajorInterval || Axis.MinorTicks.HasValue); + + private bool hasValidMajorInterval => Axis.MajorInterval.HasValue && Axis.MajorInterval.Value > 0; + + // The charting component throws if the minor tick count is outside [MIN_MINOR_TICKS, MAX_MINOR_TICKS]. An out-of-range + // value is flagged to the user by the axis validation rules; here we simply fall back to the automatic count. + private bool hasValidMinorTicks => + Axis.MinorTicks.HasValue && Axis.MinorTicks.Value >= OSPAxis.MIN_MINOR_TICKS && Axis.MinorTicks.Value <= OSPAxis.MAX_MINOR_TICKS; + + private void applyManualTickSettings() + { + if (hasValidMajorInterval) + { + _axisView.NumericScaleOptions.AutoGrid = false; + _axisView.NumericScaleOptions.GridSpacing = Axis.MajorInterval.Value; + _axisView.NumericScaleOptions.GridAlignment = NumericGridAlignment.Ones; + _axisView.NumericScaleOptions.MeasureUnit = NumericMeasureUnit.Ones; + } + else + { + _axisView.NumericScaleOptions.AutoGrid = true; + } + + if (hasValidMinorTicks) + { + _axisView.MinorCount = Axis.MinorTicks.Value; + _axisView.Tickmarks.MinorVisible = true; + } + else + { + _axisView.MinorCount = _defaultMinorTickCount; + } + } + private void configureAxisScale(int axisWidthInPixel) { + if (hasManualTickSettings) + { + applyManualTickSettings(); + return; + } + var ticksConfig = new TicksConfig { AutoScale = true }; if (shouldApplyPreferredMinorTicks()) diff --git a/src/OSPSuite.UI/Views/Charts/AxisSettingsView.cs b/src/OSPSuite.UI/Views/Charts/AxisSettingsView.cs index 37fd8bb04..35ed07d5e 100644 --- a/src/OSPSuite.UI/Views/Charts/AxisSettingsView.cs +++ b/src/OSPSuite.UI/Views/Charts/AxisSettingsView.cs @@ -88,6 +88,10 @@ public override void InitializeBinding() createColumn(x => x.Max, AxisOptionsColumns.Max); + createColumn(x => x.MajorInterval, AxisOptionsColumns.MajorInterval); + + createColumn(x => x.MinorTicks, AxisOptionsColumns.MinorTicks); + createColumn(x => x.GridLines, AxisOptionsColumns.GridLines, _gridLinesRepository); createColumn(x => x.DefaultColor, AxisOptionsColumns.DefaultColor, _colorRepository, toolTip: ToolTips.DefaultCurveColor) diff --git a/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.Designer.cs b/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.Designer.cs index 69170ab0a..f5d4889ef 100644 --- a/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.Designer.cs +++ b/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.Designer.cs @@ -44,6 +44,8 @@ private void InitializeComponent() this.defaultColorColorEdit = new OSPSuite.UI.Controls.UxColorPickEditWithHistory(); this.axisTypeTextBox = new DevExpress.XtraEditors.TextEdit(); this.maxTextBox = new DevExpress.XtraEditors.TextEdit(); + this.majorIntervalTextBox = new DevExpress.XtraEditors.TextEdit(); + this.minorTicksTextBox = new DevExpress.XtraEditors.TextEdit(); this.dimensionComboBox = new OSPSuite.UI.Controls.UxComboBoxEdit(); this.axisTypeLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); this.defaultLineStyleLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); @@ -54,6 +56,8 @@ private void InitializeComponent() this.captionLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); this.minLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); this.maxLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); + this.majorIntervalLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); + this.minorTicksLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); this.gridLinesLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); this.defaultColorLayoutControlItem = new DevExpress.XtraLayout.LayoutControlItem(); this.emptySpaceItem1 = new DevExpress.XtraLayout.EmptySpaceItem(); @@ -71,6 +75,8 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.defaultColorColorEdit.Properties)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.axisTypeTextBox.Properties)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.maxTextBox.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.majorIntervalTextBox.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.minorTicksTextBox.Properties)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.dimensionComboBox.Properties)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.axisTypeLayoutControlItem)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.defaultLineStyleLayoutControlItem)).BeginInit(); @@ -81,6 +87,8 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.captionLayoutControlItem)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.minLayoutControlItem)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.maxLayoutControlItem)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.majorIntervalLayoutControlItem)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.minorTicksLayoutControlItem)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridLinesLayoutControlItem)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.defaultColorLayoutControlItem)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem1)).BeginInit(); @@ -98,13 +106,15 @@ private void InitializeComponent() this.uxLayoutControl1.Controls.Add(this.captionTextBox); this.uxLayoutControl1.Controls.Add(this.minTextBox); this.uxLayoutControl1.Controls.Add(this.maxTextBox); + this.uxLayoutControl1.Controls.Add(this.majorIntervalTextBox); + this.uxLayoutControl1.Controls.Add(this.minorTicksTextBox); this.uxLayoutControl1.Controls.Add(this.gridLinesCheckEdit); this.uxLayoutControl1.Controls.Add(this.defaultColorColorEdit); this.uxLayoutControl1.Dock = System.Windows.Forms.DockStyle.Fill; this.uxLayoutControl1.Location = new System.Drawing.Point(0, 0); this.uxLayoutControl1.Name = "uxLayoutControl1"; this.uxLayoutControl1.Root = this.layoutControlGroup1; - this.uxLayoutControl1.Size = new System.Drawing.Size(337, 321); + this.uxLayoutControl1.Size = new System.Drawing.Size(337, 369); this.uxLayoutControl1.TabIndex = 38; this.uxLayoutControl1.Text = "uxLayoutControl1"; // @@ -118,6 +128,8 @@ private void InitializeComponent() this.gridLinesLayoutControlItem, this.maxLayoutControlItem, this.minLayoutControlItem, + this.majorIntervalLayoutControlItem, + this.minorTicksLayoutControlItem, this.scalingLayoutControlItem, this.unitLayoutControlItem, this.dimensionLayoutControlItem, @@ -127,7 +139,7 @@ private void InitializeComponent() this.emptySpaceItem1}); this.layoutControlGroup1.Location = new System.Drawing.Point(0, 0); this.layoutControlGroup1.Name = "layoutControlGroup1"; - this.layoutControlGroup1.Size = new System.Drawing.Size(337, 321); + this.layoutControlGroup1.Size = new System.Drawing.Size(337, 369); this.layoutControlGroup1.TextVisible = false; // // scalingComboBox @@ -161,7 +173,7 @@ private void InitializeComponent() // gridLinesCheckEdit // this.gridLinesCheckEdit.AllowClicksOutsideControlArea = false; - this.gridLinesCheckEdit.Location = new System.Drawing.Point(216, 204); + this.gridLinesCheckEdit.Location = new System.Drawing.Point(216, 252); this.gridLinesCheckEdit.Name = "gridLinesCheckEdit"; this.gridLinesCheckEdit.Properties.AllowFocused = false; this.gridLinesCheckEdit.Properties.Caption = ""; @@ -171,7 +183,7 @@ private void InitializeComponent() // // defaultLineSytleComboBox // - this.defaultLineSytleComboBox.Location = new System.Drawing.Point(216, 251); + this.defaultLineSytleComboBox.Location = new System.Drawing.Point(216, 299); this.defaultLineSytleComboBox.Name = "defaultLineSytleComboBox"; this.defaultLineSytleComboBox.Properties.Buttons.AddRange(new DevExpress.XtraEditors.Controls.EditorButton[] { new DevExpress.XtraEditors.Controls.EditorButton(DevExpress.XtraEditors.Controls.ButtonPredefines.Combo)}); @@ -200,7 +212,7 @@ private void InitializeComponent() // defaultColorColorEdit // this.defaultColorColorEdit.EditValue = System.Drawing.Color.Empty; - this.defaultColorColorEdit.Location = new System.Drawing.Point(216, 227); + this.defaultColorColorEdit.Location = new System.Drawing.Point(216, 275); this.defaultColorColorEdit.Name = "defaultColorColorEdit"; this.defaultColorColorEdit.Properties.AutomaticColor = System.Drawing.Color.Black; this.defaultColorColorEdit.Properties.Buttons.AddRange(new DevExpress.XtraEditors.Controls.EditorButton[] { @@ -226,9 +238,25 @@ private void InitializeComponent() this.maxTextBox.Size = new System.Drawing.Size(109, 20); this.maxTextBox.StyleController = this.uxLayoutControl1; this.maxTextBox.TabIndex = 14; - // + // + // majorIntervalTextBox + // + this.majorIntervalTextBox.Location = new System.Drawing.Point(216, 204); + this.majorIntervalTextBox.Name = "majorIntervalTextBox"; + this.majorIntervalTextBox.Size = new System.Drawing.Size(109, 20); + this.majorIntervalTextBox.StyleController = this.uxLayoutControl1; + this.majorIntervalTextBox.TabIndex = 15; + // + // minorTicksTextBox + // + this.minorTicksTextBox.Location = new System.Drawing.Point(216, 228); + this.minorTicksTextBox.Name = "minorTicksTextBox"; + this.minorTicksTextBox.Size = new System.Drawing.Size(109, 20); + this.minorTicksTextBox.StyleController = this.uxLayoutControl1; + this.minorTicksTextBox.TabIndex = 16; + // // dimensionComboBox - // + // this.dimensionComboBox.Location = new System.Drawing.Point(216, 84); this.dimensionComboBox.Name = "dimensionComboBox"; this.dimensionComboBox.Properties.Buttons.AddRange(new DevExpress.XtraEditors.Controls.EditorButton[] { @@ -251,7 +279,7 @@ private void InitializeComponent() // this.defaultLineStyleLayoutControlItem.Control = this.defaultLineSytleComboBox; this.defaultLineStyleLayoutControlItem.CustomizationFormText = "layoutControlItem2"; - this.defaultLineStyleLayoutControlItem.Location = new System.Drawing.Point(0, 239); + this.defaultLineStyleLayoutControlItem.Location = new System.Drawing.Point(0, 287); this.defaultLineStyleLayoutControlItem.Name = "defaultLineStyleLayoutControlItem"; this.defaultLineStyleLayoutControlItem.Size = new System.Drawing.Size(317, 24); this.defaultLineStyleLayoutControlItem.Text = "defaultLineStyleLayoutControlItem"; @@ -326,12 +354,32 @@ private void InitializeComponent() this.maxLayoutControlItem.Size = new System.Drawing.Size(317, 24); this.maxLayoutControlItem.Text = "maxLayoutControlItem"; this.maxLayoutControlItem.TextSize = new System.Drawing.Size(200, 13); - // + // + // majorIntervalLayoutControlItem + // + this.majorIntervalLayoutControlItem.Control = this.majorIntervalTextBox; + this.majorIntervalLayoutControlItem.CustomizationFormText = "majorIntervalLayoutControlItem"; + this.majorIntervalLayoutControlItem.Location = new System.Drawing.Point(0, 192); + this.majorIntervalLayoutControlItem.Name = "majorIntervalLayoutControlItem"; + this.majorIntervalLayoutControlItem.Size = new System.Drawing.Size(317, 24); + this.majorIntervalLayoutControlItem.Text = "majorIntervalLayoutControlItem"; + this.majorIntervalLayoutControlItem.TextSize = new System.Drawing.Size(200, 13); + // + // minorTicksLayoutControlItem + // + this.minorTicksLayoutControlItem.Control = this.minorTicksTextBox; + this.minorTicksLayoutControlItem.CustomizationFormText = "minorTicksLayoutControlItem"; + this.minorTicksLayoutControlItem.Location = new System.Drawing.Point(0, 216); + this.minorTicksLayoutControlItem.Name = "minorTicksLayoutControlItem"; + this.minorTicksLayoutControlItem.Size = new System.Drawing.Size(317, 24); + this.minorTicksLayoutControlItem.Text = "minorTicksLayoutControlItem"; + this.minorTicksLayoutControlItem.TextSize = new System.Drawing.Size(200, 13); + // // gridLinesLayoutControlItem - // + // this.gridLinesLayoutControlItem.Control = this.gridLinesCheckEdit; this.gridLinesLayoutControlItem.CustomizationFormText = "layoutControlItem8"; - this.gridLinesLayoutControlItem.Location = new System.Drawing.Point(0, 192); + this.gridLinesLayoutControlItem.Location = new System.Drawing.Point(0, 240); this.gridLinesLayoutControlItem.Name = "gridLinesLayoutControlItem"; this.gridLinesLayoutControlItem.Size = new System.Drawing.Size(317, 23); this.gridLinesLayoutControlItem.Text = "gridLinesLayoutControlItem"; @@ -341,7 +389,7 @@ private void InitializeComponent() // this.defaultColorLayoutControlItem.Control = this.defaultColorColorEdit; this.defaultColorLayoutControlItem.CustomizationFormText = "layoutControlItem7"; - this.defaultColorLayoutControlItem.Location = new System.Drawing.Point(0, 215); + this.defaultColorLayoutControlItem.Location = new System.Drawing.Point(0, 263); this.defaultColorLayoutControlItem.Name = "defaultColorLayoutControlItem"; this.defaultColorLayoutControlItem.Size = new System.Drawing.Size(317, 24); this.defaultColorLayoutControlItem.Text = "defaultColorLayoutControlItem"; @@ -350,7 +398,7 @@ private void InitializeComponent() // emptySpaceItem1 // this.emptySpaceItem1.AllowHotTrack = false; - this.emptySpaceItem1.Location = new System.Drawing.Point(0, 263); + this.emptySpaceItem1.Location = new System.Drawing.Point(0, 311); this.emptySpaceItem1.Name = "emptySpaceItem1"; this.emptySpaceItem1.Size = new System.Drawing.Size(317, 38); this.emptySpaceItem1.TextSize = new System.Drawing.Size(0, 0); @@ -359,7 +407,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(337, 367); + this.ClientSize = new System.Drawing.Size(337, 415); this.Controls.Add(this.uxLayoutControl1); this.Margin = new System.Windows.Forms.Padding(0); this.Name = "SingleAxisSettingsView"; @@ -378,6 +426,8 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.defaultColorColorEdit.Properties)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.axisTypeTextBox.Properties)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.maxTextBox.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.majorIntervalTextBox.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.minorTicksTextBox.Properties)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.dimensionComboBox.Properties)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.axisTypeLayoutControlItem)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.defaultLineStyleLayoutControlItem)).EndInit(); @@ -388,6 +438,8 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.captionLayoutControlItem)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.minLayoutControlItem)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.maxLayoutControlItem)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.majorIntervalLayoutControlItem)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.minorTicksLayoutControlItem)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.gridLinesLayoutControlItem)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.defaultColorLayoutControlItem)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem1)).EndInit(); @@ -408,6 +460,8 @@ private void InitializeComponent() private TextEdit captionTextBox; private TextEdit minTextBox; private TextEdit maxTextBox; + private TextEdit majorIntervalTextBox; + private TextEdit minorTicksTextBox; private UxCheckEdit gridLinesCheckEdit; private UxColorPickEditWithHistory defaultColorColorEdit; private DevExpress.XtraLayout.LayoutControlGroup layoutControlGroup1; @@ -415,6 +469,8 @@ private void InitializeComponent() private DevExpress.XtraLayout.LayoutControlItem defaultColorLayoutControlItem; private DevExpress.XtraLayout.LayoutControlItem gridLinesLayoutControlItem; private DevExpress.XtraLayout.LayoutControlItem maxLayoutControlItem; + private DevExpress.XtraLayout.LayoutControlItem majorIntervalLayoutControlItem; + private DevExpress.XtraLayout.LayoutControlItem minorTicksLayoutControlItem; private DevExpress.XtraLayout.LayoutControlItem minLayoutControlItem; private DevExpress.XtraLayout.LayoutControlItem scalingLayoutControlItem; private DevExpress.XtraLayout.LayoutControlItem unitLayoutControlItem; diff --git a/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.cs b/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.cs index 0b2e34713..a288b356f 100644 --- a/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.cs +++ b/src/OSPSuite.UI/Views/Charts/SingleAxisSettingsView.cs @@ -59,6 +59,12 @@ public override void InitializeBinding() _screenBinder.Bind(x => x.Max) .To(maxTextBox); + _screenBinder.Bind(x => x.MajorInterval) + .To(majorIntervalTextBox); + + _screenBinder.Bind(x => x.MinorTicks) + .To(minorTicksTextBox); + _screenBinder.Bind(x => x.GridLines) .To(gridLinesCheckEdit); @@ -81,6 +87,8 @@ public override void InitializeResources() scalingLayoutControlItem.Text = Captions.Scaling.FormatForLabel(); minLayoutControlItem.Text = Captions.AxisMinimum.FormatForLabel(); maxLayoutControlItem.Text = Captions.AxisMaximum.FormatForLabel(); + majorIntervalLayoutControlItem.Text = Captions.AxisMajorInterval.FormatForLabel(); + minorTicksLayoutControlItem.Text = Captions.AxisMinorTicks.FormatForLabel(); gridLinesLayoutControlItem.Text = Captions.GridLines.FormatForLabel(); defaultColorLayoutControlItem.Text = Captions.DefaultColor.FormatForLabel(); defaultLineStyleLayoutControlItem.Text = Captions.DefaultLineStyle.FormatForLabel(); diff --git a/tests/OSPSuite.Core.IntegrationTests/Serializers/AxisXmlSerializerSpecs.cs b/tests/OSPSuite.Core.IntegrationTests/Serializers/AxisXmlSerializerSpecs.cs index 86e585c4e..502bc720a 100644 --- a/tests/OSPSuite.Core.IntegrationTests/Serializers/AxisXmlSerializerSpecs.cs +++ b/tests/OSPSuite.Core.IntegrationTests/Serializers/AxisXmlSerializerSpecs.cs @@ -13,7 +13,7 @@ public class AxisXmlSerializerSpecs : ModelingXmlSerializerBaseSpecs public void TestSerialization() { var dimensionFactory = IoC.Resolve(); - var x1 = new Axis(AxisTypes.Y) {Dimension = DimensionMolarConcentration}; + var x1 = new Axis(AxisTypes.Y) {Dimension = DimensionMolarConcentration, MajorInterval = 25, MinorTicks = 4}; //Ensure that we use the actural merged dimension for this axis x1.Dimension = dimensionFactory.MergedDimensionFor(x1); x1.UnitName = DimensionMassConcentration.Unit("mg/l").Name; diff --git a/tests/OSPSuite.Core.Tests/Domain/AxisSpecs.cs b/tests/OSPSuite.Core.Tests/Domain/AxisSpecs.cs index 60b37b96d..86800e154 100644 --- a/tests/OSPSuite.Core.Tests/Domain/AxisSpecs.cs +++ b/tests/OSPSuite.Core.Tests/Domain/AxisSpecs.cs @@ -37,6 +37,33 @@ public void invalid_for_log_max_equal_to_0() } + public class When_validating_axis_major_interval_and_minor_count : concern_for_Axis + { + [Observation] + public void invalid_for_a_major_interval_of_zero() + { + new Axis(AxisTypes.X) { MajorInterval = 0 }.IsValid().ShouldBeFalse(); + } + + [Observation] + public void invalid_for_a_minor_count_of_zero() + { + new Axis(AxisTypes.X) { MinorTicks = 0 }.IsValid().ShouldBeFalse(); + } + + [Observation] + public void invalid_for_a_minor_count_of_one_hundred() + { + new Axis(AxisTypes.X) { MinorTicks = 100 }.IsValid().ShouldBeFalse(); + } + + [Observation] + public void valid_for_a_positive_major_interval_and_minor_count() + { + new Axis(AxisTypes.X) { MajorInterval = 2.5F, MinorTicks = 3 }.IsValid().ShouldBeTrue(); + } + } + public class When_updating_an_axis_properties_from_another : concern_for_Axis { private Axis _sourceAxis; @@ -54,6 +81,8 @@ protected override void Context() GridLines = false, Min = -1, Max = 300, + MajorInterval = 50, + MinorTicks = 4, DefaultLineStyle = LineStyles.DashDot, DefaultColor = Color.Azure, Visible = false @@ -78,6 +107,8 @@ public void the_properties_from_the_source_should_match_the_target_of_update() _sourceAxis.GridLines.ShouldBeEqualTo(sut.GridLines); _sourceAxis.Min.ShouldBeEqualTo(sut.Min); _sourceAxis.Max.ShouldBeEqualTo(sut.Max); + _sourceAxis.MajorInterval.ShouldBeEqualTo(sut.MajorInterval); + _sourceAxis.MinorTicks.ShouldBeEqualTo(sut.MinorTicks); _sourceAxis.DefaultLineStyle.ShouldBeEqualTo(sut.DefaultLineStyle); _sourceAxis.DefaultColor.ShouldBeEqualTo(sut.DefaultColor); _sourceAxis.Visible.ShouldBeEqualTo(sut.Visible); diff --git a/tests/OSPSuite.Core.Tests/Mappers/AxisMapperSpecs.cs b/tests/OSPSuite.Core.Tests/Mappers/AxisMapperSpecs.cs index 5fbd35f61..87d60bf8c 100644 --- a/tests/OSPSuite.Core.Tests/Mappers/AxisMapperSpecs.cs +++ b/tests/OSPSuite.Core.Tests/Mappers/AxisMapperSpecs.cs @@ -35,6 +35,8 @@ protected override Task Context() Visible = true, Min = 60, //min Max = 120, + MajorInterval = 30, + MinorTicks = 5, DefaultColor = Color.AntiqueWhite, DefaultLineStyle = LineStyles.Solid }; @@ -70,6 +72,13 @@ public void should_have_saved_the_min_and_max_as_is_as_they_are_already_saved_in _snapshot.Min.ShouldBeEqualTo(60); _snapshot.Max.ShouldBeEqualTo(120); } + + [Observation] + public void should_have_saved_the_major_interval_and_minor_count() + { + _snapshot.MajorInterval.ShouldBeEqualTo(30); + _snapshot.MinorTicks.ShouldBeEqualTo(5); + } } public class When_mapping_an_axis_to_snapshot_that_does_not_have_a_dimension : concern_for_AxisMapper @@ -154,5 +163,12 @@ public void should_have_loaded_the_min_and_max_from_snapshot_as_is() _newAxis.Min.ShouldBeEqualTo(60); _newAxis.Max.ShouldBeEqualTo(120); } + + [Observation] + public void should_have_loaded_the_major_interval_and_minor_count_from_snapshot() + { + _newAxis.MajorInterval.ShouldBeEqualTo(30); + _newAxis.MinorTicks.ShouldBeEqualTo(5); + } } } \ No newline at end of file diff --git a/tests/OSPSuite.HelpersForTests/AssertForSpecs.cs b/tests/OSPSuite.HelpersForTests/AssertForSpecs.cs index 23871e7b6..91d306f3c 100644 --- a/tests/OSPSuite.HelpersForTests/AssertForSpecs.cs +++ b/tests/OSPSuite.HelpersForTests/AssertForSpecs.cs @@ -518,6 +518,8 @@ public static void AreEqualAxis(Axis x1, Axis x2) AssertAreEqual(x1.GridLines, x2.GridLines); AssertAreEqual(x1.Min, x2.Min); AssertAreEqual(x1.Max, x2.Max); + AssertAreEqual(x1.MajorInterval, x2.MajorInterval); + AssertAreEqual(x1.MinorTicks, x2.MinorTicks); AssertAreEqual(x1.NumberMode, x2.NumberMode); AssertAreEqual(x1.Scaling, x2.Scaling); AssertAreEqual(x1.Visible, x2.Visible); diff --git a/tests/OSPSuite.UI.Tests/Binders/AxisBinderSpecs.cs b/tests/OSPSuite.UI.Tests/Binders/AxisBinderSpecs.cs index 6263036e3..89697758a 100644 --- a/tests/OSPSuite.UI.Tests/Binders/AxisBinderSpecs.cs +++ b/tests/OSPSuite.UI.Tests/Binders/AxisBinderSpecs.cs @@ -3,6 +3,7 @@ using OSPSuite.BDDHelper; using OSPSuite.BDDHelper.Extensions; using OSPSuite.Core.Chart; +using OSPSuite.Core.Domain; using OSPSuite.UI.Controls; using OSPSuite.Utility.Format; using Axis = OSPSuite.Core.Chart.Axis; @@ -117,4 +118,109 @@ public void the_min_should_be_set_to_equal_the_max() _axis.Min.ShouldBeEqualTo(_axis.Max); } } + + public class When_refreshing_adapter_and_a_linear_axis_has_a_manual_major_interval_and_minor_count : concern_for_AxisBinder + { + protected override void Context() + { + base.Context(); + _axis.MajorInterval = 2F; + _axis.MinorTicks = 3; + } + + protected override void Because() + { + sut.RefreshRange(sideMarginsEnabled: true, diagramSize: new Size(100, 100)); + } + + [Observation] + public void should_disable_the_auto_grid_and_apply_the_major_interval_as_grid_spacing() + { + _uxChartControl.XYDiagram.AxisY.NumericScaleOptions.AutoGrid.ShouldBeFalse(); + _uxChartControl.XYDiagram.AxisY.NumericScaleOptions.GridSpacing.ShouldBeEqualTo(2); + } + + [Observation] + public void should_apply_the_minor_count() + { + _uxChartControl.XYDiagram.AxisY.MinorCount.ShouldBeEqualTo(3); + } + } + + public class When_refreshing_adapter_and_a_linear_axis_has_no_manual_tick_settings : concern_for_AxisBinder + { + protected override void Because() + { + sut.RefreshRange(sideMarginsEnabled: true, diagramSize: new Size(100, 100)); + } + + [Observation] + public void should_let_the_axis_calculate_the_grid_automatically() + { + _uxChartControl.XYDiagram.AxisY.NumericScaleOptions.AutoGrid.ShouldBeTrue(); + } + } + + public class When_refreshing_adapter_and_a_linear_axis_has_an_out_of_range_minor_count : concern_for_AxisBinder + { + protected override void Context() + { + base.Context(); + _axis.MajorInterval = 2F; + _axis.MinorTicks = 0; + } + + protected override void Because() + { + sut.RefreshRange(sideMarginsEnabled: true, diagramSize: new Size(100, 100)); + } + + [Observation] + public void should_fall_back_to_the_default_minor_count_instead_of_throwing() + { + _uxChartControl.XYDiagram.AxisY.MinorCount.ShouldBeEqualTo(4); + } + } + + public class When_refreshing_adapter_and_a_linear_axis_has_a_non_positive_major_interval : concern_for_AxisBinder + { + protected override void Context() + { + base.Context(); + _axis.MajorInterval = 0; + } + + protected override void Because() + { + sut.RefreshRange(sideMarginsEnabled: true, diagramSize: new Size(100, 100)); + } + + [Observation] + public void should_fall_back_to_the_automatic_grid_instead_of_applying_an_invalid_spacing() + { + _uxChartControl.XYDiagram.AxisY.NumericScaleOptions.AutoGrid.ShouldBeTrue(); + } + } + + public class When_refreshing_adapter_and_a_logarithmic_axis_has_manual_tick_settings : concern_for_AxisBinder + { + protected override void Context() + { + base.Context(); + _axis.Scaling = Scalings.Log; + _axis.MajorInterval = 2F; + _axis.MinorTicks = 3; + } + + protected override void Because() + { + sut.RefreshRange(sideMarginsEnabled: true, diagramSize: new Size(100, 100)); + } + + [Observation] + public void should_ignore_the_manual_tick_settings_and_keep_the_logarithmic_minor_count() + { + _uxChartControl.XYDiagram.AxisY.MinorCount.ShouldBeEqualTo(8); + } + } }