I am trying to vertically align text in ActiveReports 13. I am creating this report in code. The example I am trying to match is this:
However, after my efforts to do it, my result ends up looking like this:
The spacing seems to break at odd spots, even though the text in the data source is correct. The code I am using is:
for (int i = 0; i < dataTable.Columns.Count; i++)
{
ctl = new GrapeCity.ActiveReports.SectionReportModel.TextBox();
ctl.Name = columnName;
ctl.Text = dt.Columns[i].ColumnName;
ctl.Location = new PointF((0.3f * i) + 1.7f, 0.4f);
ctl.Size = new SizeF(0.3f, 1.0f);
ctl.VerticalText = true;
ctl.VerticalAlignment = GrapeCity.ActiveReports.Drawing.VerticalTextAlignment.Middle;
}
Increasing the width doesn't help. If I narrow the Size value and adjust the CharacterSpacings, the text spacing problem improves, but the background is narrowed and the text alignment shifts - the characters are rotated 90 degrees:
Any suggestions?
I found the way to solve this was to convert the TextBox to a Label, and then change the Angle property to 2700. Setting the Alignment to "Right" also aligned the text just how I wanted it:
ctl = new GrapeCity.ActiveReports.SectionReportModel.Label();
ctl.Angle = 2700;
ctl.Alignment = GrapeCity.ActiveReports.Drawing.TextAlignment.Right;
Related
This question already has an answer here:
Making a 4 sided Graph / 4 sided (Cartesian) grid In Visual Studio
(1 answer)
Closed 3 years ago.
I want to display a single dashed line along Y-axis positioned at the middle vertically. I thought of it as a trivial problem but it seems that either I don't know how to do it or it is not available as a direct option.
This is what I've tried so far
chart1.ChartAreas[0].AxisY.Enabled = AxisEnabled.True;
chart1.ChartAreas[0].AxisY.LineWidth = 1;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = true;
chart1.ChartAreas[0].AxisY.MinorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.IsStartedFromZero = true;
chart1.ChartAreas[0].AxisY.MajorGrid.IntervalType = DateTimeIntervalType.Number;
chart1.ChartAreas[0].AxisY.MajorGrid.IntervalOffsetType = DateTimeIntervalType.Number;
chart1.ChartAreas[0].AxisY.MajorGrid.Interval = 5;
chart1.ChartAreas[0].AxisY.Interval = 5;
chart1.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.Black;
chart1.ChartAreas[0].AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dash;
Note that I know interval property is not set according to what I want but the problem is no matter what value I set for chart1.ChartAreas[0].AxisY.Interval property chart control simply draws so many lines along Y-axis. I'd even tried normalizing my input to the range [-50, 50] and rounded them to integers but still the results are same. However, my logic of interval is working with X-axis and yielding expected results but not for Y-axes.
So I got the solution based on the example provided by TaW. Pasting here the example code I used, may it help someone.
ChartArea CA = chart1.ChartAreas[0];
Series S1 = chart1.Series[0];
S1.ChartType = SeriesChartType.Line;
CA.AxisY.Maximum = 100;
CA.AxisY.Minimum = -100;
CA.AxisY.Crossing = 0;
CA.AxisY.Interval = 10;
CA.AxisY.LineWidth = 1;
CA.AxisY.MajorGrid.Enabled = false;
CA.AxisY.MinorTickMark.Enabled = false;
The trick was to disable gridlines and show a line at the middle by setting Crossing = 0 as suggested by TaW. Note that it's only for Y-axis if anyone wants to have it for both axis than need to apply the same properties to X-axis.
By default, the angles on the polar chart go from 0 to 360 in a clockwise direction but I want them to go counter clockwise (anticlockwise)
chart.ChartAreas[0].AxisX.Title = "Elevation";
chart.ChartAreas[0].AxisY.Title = "Power(dBm)";
chart.ChartAreas[0].BackColor = System.Drawing.Color.FromArgb(211, 223, 240);
chart.ChartAreas[0].BorderColor = System.Drawing.Color.FromArgb(26, 59, 105);
chart.ChartAreas[0].AxisY.IsStartedFromZero = false;
chart.PrePaint += new EventHandler<ChartPaintEventArgs>(chart_prePaint);
I've tried changing the labels per some example code I found like this:
CustomLabelsCollection labels = chart.ChartAreas[0].AxisX.CustomLabels;
if (labels == null) return;
for (int i = 0; i < labels.Count - 1; i++)
{
if (labels[0].Text == "360") break;
labels[i].Text = (360 - int.Parse(labels[i].Text)).ToString();
labels[i].ToolTip = "Angle in Degrees";
}
The code changes the labels in the object but not on the graph. And every time the event is fired and we come back into this event handler, the labels have been reset to the way it was originally.And the tooltips have been reset.
To add to the confusion, I'm not sure why the CustomLabels object is populated in the first place - I didn't do it.
Any idea why the changes are having no effect?
Thanks in advance!
If you want something like this:..
..CustomLabels are indeed a way to achieve it. I couldn't find a way make the axis reverse itself..
Here is the C# code I used:
Axis ay = chart.ChartAreas[0].AxisY;
ay.LabelStyle.Enabled = false;
Axis ax = chart.ChartAreas[0].AxisX;
ax.CustomLabels.Clear();
int step = (int)ax.Interval;
if (step == 0) step = 30;
for (int i = 0; i < 360; i+=step)
{
int a = 360 - i; // the angle to target
var cl = new CustomLabel();
cl.Text = a + "°";
cl.FromPosition = a + 0.01; // create a small..
cl.ToPosition = a - 0.01; // ..space to place the label !
ax.CustomLabels.Add(cl);
}
Note that only the Labels are reversed, not the values!
To start at 0 simply change the loop condition to <= and check for i>0 before creating the labels!
If you didn't set an Interval I use a default interval of 30; change as needed!
The CustomLabels collection by default is created, so it isn't null but it is empty (Count==0). If you didn't create any, then there are none and the original AxisLabels are showing. (Only one type be shown!)
Unless you have a really good reason, like very dynamic data, you should not add of modify anything in a xxxPaint event! They may be called quite often and these event are really just for drawing. ((And sometimes for measuring))
i got a Little "Problem", i want to create a Chart looking like this:
So basically
Series 1 = Normal bar Chart. Color green if it Ends before the "time max" (series2) Series 2 = just a DataPoint / Marker on top of series 1 items.
I am struggling with this though...
my Code:
chart_TimeChart.Series.Clear();
string series_timeneeded = "Time Needed";
chart_TimeChart.Series.Add(series_timeneeded);
chart_TimeChart.Series[series_timeneeded]["PixelPointWidth"] = "5";
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Size = 10;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = true;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
chart_TimeChart.Series[series_timeneeded].BorderWidth = 2;
chart_TimeChart.Series[series_timeneeded].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_timeneeded].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[series_timeneeded].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
chart_TimeChart.Series[series_timeneeded].Points.AddXY("item"+ " " + (i + 1).ToString(), DateTime.Now.Add(Timespans[i]));
}
chart_TimeChart.Series.Add(series_FinishTime);
chart_TimeChart.Series[series_FinishTime].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_FinishTime].BorderWidth = 0;
chart_TimeChart.Series[series_FinishTime].MarkerSize = 15;
chart_TimeChart.Series[series_FinishTime].MarkerStyle = MarkerStyle.Square;
chart_TimeChart.Series[series_FinishTime].MarkerColor = Color.Black;
chart_TimeChart.Series[series_FinishTime].YValueType = ChartValueType.DateTime;
chart_TimeChart.Series[series_FinishTime].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
DateTime YPosition = GetFinishTime(i);
chart_TimeChart.Series[series_FinishTime].Points.AddXY("item"+ " " +(i+1).ToString(), YPosition);
}
but this only Displays the 2nd series on top of the first one but the first one isnt visible anymore. The Maker of series 2 isnt shown but instead the bar is (eventhough i made borderwidth to 0). In my opinion/thinking i just have to make the "bar" of series 2 invisible and just Show the marker Points for series 2.
Any ideas?
Update:
string seriesname = Name+ i.ToString();
chart_TimeChart.Series.Add(seriesname);
chart_TimeChart.Series[seriesname].SetCustomProperty("DrawSideBySide", "false");
chart_TimeChart.Series[seriesname].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].ChartType = SeriesChartType.StackedBar; //Y and X are exchanged
chart_TimeChart.Series[seriesname].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[seriesname].XValueType = ChartValueType.String;
DateTime TimeNeeded = DateTime.Now.Add(List_AllLiniengroupsTimespans[k][i]);
DateTime TimeMax = GetFinishTime(k, i);
TimeSpan TimeDifference = TimeNeeded - TimeMax;
if (TimeNeeded > TimeMax) //All good
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //Time till finish
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //time left
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Red;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax > TimeNeeded) //wont make it in time
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //time till still okay
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //Time that is too much
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Green;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax == TimeNeeded) //fits exactly
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded);
chart_TimeChart.Series[seriesname].Points[0].Color = Color.DarkOrange;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
}
the Code will be displayed as:
but i want it to look like this:
!! See the update below !!
If you really want to create a StackedBar chart, your chart has two issues:
If you want to stack datapoints they need to have meaningful x-values; without them how can it know what to stack on each other?
You add strings, which look fine but simply don't work. That is because the DataPoint.XValue field is double and when you add string into it it is set to 0 !! Your string is copied to the Label but otherwise lost.
So you need to come up with a suitable numeric value you use for the x-values..
And you also need to group the series you want to stack. For this there is a special property called StackedGroupName which serves to group those series that shall be stacked.
Here is how you can use it:
yourSeries1.SetCustomProperty("StackedGroupName", "Group1");
For a full example see this post !
It also shows one way of setting the Labels with string values of your choice..
This is the way to go for real StackedBar charts. Your workaround may or may not work. You could try to make the colors transparent or equal to the chart's backcolor; but it won't be more than a hack, imo.
Update
I guess I have misread the question. From what I see you do not really want to create a stacked chart.
Instead you struggle with these issues:
displaying bars at the same y-spot
making some bars invisible
displaying a vertical line as a marker
Let's tackle each:
Some column types including all Bars, Columns and then some have a little known special property called DrawSideBySide.
By default is is set to Auto, which will work like True. This is usually fine as we don't want bars to sit upon each other, effectively hiding all or part of the overlaid points.
But here we do want them to share the same y-position, so we need to set the property to false for at least one Series; the others (on Auto) will follow..:
You can do it either like this:
aSeries["DrawSideBySide"] = "false";
or like this:
aSeries.SetCustomProperty("DrawSideBySide", "false");
Next we hide the overlaid Series; this is simple:
aSeries.Color = Color.Transparent;
The last issue is displaying a line marker. There is no such MarkerStyle, so we need to use a custom style. For this we need to create a suitable bitmap and add it as a NamedImage to the chart's Images collection.
This sounds more complicated than it is; however the MarkerImage will not be scaled, so we need to created suitable sizes whenever we resize the Chart or add/remove points. I will ignore this complication for now..
int pointCount = 10;
Bitmap bmp = new Bitmap(2, chart.ClientSize.Height / pointCount - 5);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Black);
NamedImage marker = new NamedImage("marker", bmp);
chart.Images.Clear(); // quick & dirty
chart.Images.Add(marker);
Here is the result:
A few notes:
I would recommend to use variables for all chart elements you refer to repeatedly instead of using indexed references all the time. Less code, easier to read, a lot easier to maintain, and probably better performance.
Since your code called for the visible datapoints to be either red or green the Legend will not show a good representation. See here for an example of drawing a multi-colored legend item..
I used the chart height; this is not really recommended as there may be Titles or Legends or even more ChartAreas; instead you should use the height of the ChartArea, or even more precise, the height of the InnerPlotPosition. You would need to convert those from percentages to pixels. Not too hard, see below or see here
or here for more examples!
The markers should be adapted from the Resize and probably from the AxisViewChanged events. Putting it in a nice function to call (e.g. void setMarkerImage(Chart chart, Series s, string name, int width, Color c)) is always a good idea.
If you need to adapt the size of the marker image repeatedly, you may want to write better code for clearing the old one; this should include disposing of the Bitmap that was used before..
Here is an example:
var oldni = chart.Images.FindByName("marker");
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
In some situations one needs to nudge the Chart to update some of its properties; RecalculateAxesScale is one such nudge.
Example for calculating a suitable marker height:
ChartArea ca = chart.ChartAreas[0];
ca.RecalculateAxesScale();
float cah = ca.Position.Height;
float iph = ca.InnerPlotPosition.Height;
float h = chart3.ClientSize.Height * cah / 100f * iph / 100f;
int mh = (int)(h / s.Points.Count);
Final note: The original answer stressed the importance of using meaningful x-values. Strings are useless! This was important for stacking bars; but it is equally important now, when we want bars to sit at the same vertical positions! Adding the x-values as strings is again resulting in nonsense..
(Since we have Bars the x-values go along the vertical axis and vice versa..)
I am trying to add columns and gridsplitters to a grid, but can't get the exact behavior.
After the user specifies where he wants a vertical splitter to appear:
// Get current Col0 width, size new col and existing Col0
double col0Width = LayoutRoot.ColumnDefinitions[0].ActualWidth;
double newCol0Width = col0Width - 5 - pt.X;
LayoutRoot.ColumnDefinitions[0].Width = new GridLength(newCol0Width);
// New Column 0
var c = new ColumnDefinition();
c.Width = new GridLength(pt.X);
LayoutRoot.ColumnDefinitions.Insert(0, c);// Attach GridSplitter to left edge of existing first column
var gss = new GridSplitter();
gss.Background = new SolidColorBrush(Colors.DarkSlateBlue);
gss.Width = 5; gss.Cursor = Cursors.ScrollWE;
gss.ResizeBehavior = GridResizeBehavior.BasedOnAlignment;
gss.HorizontalAlignment = HorizontalAlignment.Left;
LayoutRoot.Children.Add(gss);
// Add to current left-most colunn
Grid.SetColumn(gss, 0);
// Create new column, insert
// New Column 0
var c = new ColumnDefinition();
c.Width = new GridLength(pt.X);
LayoutRoot.ColumnDefinitions.Insert(0, c);
// Move existing content from Col 0 to new Col 1.
I can repeat this and create an arbitrary number of vertical splitters.
The required resize behavior: moving a splitter resizes only the columns immed. to the left and right of the splitter.
The current resize behavior: moving a splitter treats everything to the right of the splitter as one object, expanding or shrinking the column to the left of the splitter, while moving everything to the right. That is, if there are 3 columns, moving the left-most splitter appears to push col 2 to the right and shrinking col 3, without resizing col 2.
(I hope I explained that clearly enough.)
I have tried putting the GridSplitters in their own columns, and tried various GridResizeBehaviors, but haven't found the correct combination.
Any tips would be appreciated....
And a related question: In an event handler for GridSplitter's OnDragDelta, is there a way to stop the splitter from traveling any further in a certain direction? I would like to prevent them from shrinking the right-most column below a certain width, while allowing them to move the splitter back to the left.
Thanks.
As my comment suggested, it looks like the columns need to be '*' sized.
So, after adding the new column and splitter, I fixed up the widths like this (first clumsy cut at solution):
// Get all col widths, set appropriate '*' sizes.
foreach (ColumnDefinition col in LayoutRoot.ColumnDefinitions)
{
colWidths.Add(col.Width);
total += col.Width.Value;
Debug.WriteLine($" Width : {col.Width}");
}
Debug.WriteLine($"{total}");
for (int i = 0; i < LayoutRoot.ColumnDefinitions.Count; i++)
{
double d = colWidths[i].Value;
double ratio = d / total;
int ii = (int) (ratio * 100);
LayoutRoot.ColumnDefinitions[i].Width = new GridLength(ii, GridUnitType.Star);
}
I am trying to control the value labels for my zedgraph's x-axis. Before, the labels would "fly around" and not really stay put on the axis. They might move left or right on the axis and pop in and out of existence based on the data. Like in the picture below
I first tried to draw the labels myself when I finally found good documentation for the zedgraph library. There I found the [AXIS].Scale options of MajorStep MinorStep and BaseTic. Which if set correctly should cause the labels to stay in place and just change value as the data is added.
The issue I am running into though is that my x-axis scale is in XDate units. Which means I cannot do the simple math I was hoping to. So I have since figured a way that I think I can find the values I need using TimeSpan and DateTime. Below curMaxX and curMinX are XDate values of the current minimum and maximum x-axis values (curMinX should basically be DateTime.Now because this data is realtime)
// Setup
TimeSpan scaleDist = curMaxX.DateTime.Subtract(curMinX.DateTime);
TimeSpan major = new TimeSpan(scaleDist.Ticks / 5);
TimeSpan minor = new TimeSpan(major.Ticks / 5);
TimeSpan baseT = new TimeSpan(curMinX.DateTime.Ticks + major.Ticks);
// Setting the values
myPane.XAxis.Scale.MajorStep = new XDate(new DateTime(major.Ticks));
myPane.XAxis.Scale.MinorStep = new XDate(new DateTime(minor.Ticks));
myPane.XAxis.Scale.BaseTic = new XDate(new DateTime(baseT.Ticks));
// Print statement of the values
SDist: 00:00:10
Major: 00:00:02
Minor: 00:00:00.4000000
BaseT: 735382.07:32:34
BaseTAsDateTime: 5/30/2014 7:32:34 AM
CurMinX: 5/30/2014 7:32:32 AM
Though setting the values as such still does not achieve what I want. With this code my x-axis comes out looking like so
It is somewhat closer to what I am looking for but still off. Only a single major tic is shown, and no minor tics. I am not sure what other ways there might be to specify the values.
For the major and minor step documentation it says
For Date axes, this value is defined in units of Major/MinorUnit.
Which my MajorUnit is minutes and the MinorUnit is seconds, but I pass them a XDate value which specifies both minutes and seconds. Also, setting the values as fractions of a whole (so if I want MajorStep to be 2 seconds I'd set it as (2/60)), causes nothing to show up.
Any ideas/suggestions?
Well, I determined that it was a waste of time to try and use the default zedgraph tics, steps, etc. So I made my own method to do so based on the data currently on the graph.
After I posted this I realized trying to figure out the tics was a lost cause so I looked for other ways to do it. I was going to add text beside each point showing its values, but it became very cluttered, then I realized I can just draw the text at the bottom of the graph which is what I wanted all along. I also added a little label on the right showing the current value.
A little bit o code
private void setPointLabels()
{
// All hail the almighty pane.
GraphPane myPane = theGraph.GraphPane;
// Give us some room to draw labels
myPane.Margin.Right = 50;
myPane.Margin.Bottom = 20;
// Dont show the default stuff
myPane.XAxis.Scale.IsVisible = false;
myPane.XAxis.MajorTic.IsAllTics = false;
myPane.XAxis.MinorTic.IsAllTics = false;
// Remove the old labels
myPane.GraphObjList.Clear();
// Get the curve showing our data
LineItem myCurve = (LineItem)myPane.CurveList[0];
// Show a voltage value on the far right
PointPair pt = myCurve.Points[myCurve.Points.Count - 1];
TextObj text = new TextObj(" " + pt.Y.ToString("f2"), pt.X, pt.Y, CoordType.AxisXYScale, AlignH.Left, AlignV.Center);
text.ZOrder = ZOrder.A_InFront;
text.FontSpec.Border.IsVisible = false;
text.FontSpec.Fill.IsVisible = false;
myPane.GraphObjList.Add(text);
// Determine a hardcoded yOffset for the labels
double yOffset = -1.2;
// Determine if we need to fix the center label
int fixVal = 1;
if (xScaleSec == 10)
fixVal = 0;
// Loop over each point in the curve
for (int i = 0; i < myCurve.Points.Count; i++)
{
if (i == 0 ||
i == (myCurve.Points.Count/4) ||
i == ((myCurve.Points.Count/2)-fixVal) ||
i == ((3*myCurve.Points.Count)/4) ||
i == myCurve.Points.Count-1)
{
PointPair aPt = myCurve.Points[i];
// Add a text object just below the x-axis showing the point's x-value
XDate xVal = new XDate(aPt.X);
TextObj label = new TextObj(xVal.ToString("hh:mm.ss"), aPt.X, myPane.YAxis.Scale.Min + yOffset, CoordType.AxisXYScale, AlignH.Center, AlignV.Center);
label.ZOrder = ZOrder.A_InFront;
label.FontSpec.Fill.IsVisible = false;
label.FontSpec.Border.IsVisible = false;
myPane.GraphObjList.Add(label);
// Add a line object on the x-axis representing a tic mark
LineObj aTic = new LineObj(aPt.X, myPane.YAxis.Scale.Min - (yOffset / 4), aPt.X, myPane.YAxis.Scale.Min + (yOffset / 4));
myPane.GraphObjList.Add(aTic);
}
}
}