C# word interop tables shapes positions - c#

I'm trying to do the following:
Insert an arrow based on the value in a cell in a table.
This part, I have working just fine.
The color of the arrow should be conditional, and works equally fine.
But my problem is this:
How can I identify the correct position to place it.
I have concluded this to 2 problems:
vertically: spanning pages. For 1 page, it works, as soon as I try it for the 2nd page, it places the shapes on the first one.
Horizontally: the right posistion relative to the text that is entered in the table.
This is the code for the vertical positioning that I have now:
private float getVertical(Word.Range r)
{
return (float)r.Characters.Last.get_Information(
Microsoft.Office.Interop.Word.WdInformation.wdVerticalPositionRelativeToPage);
}
the code for horizontal(basically add the width of every cel on the same row to 2 thirds of the width of the actual cell)
private float getHorizontal(Word.Range r, Word.Table tab, int col)
{
float i1, i2 = 0.0f, i3;
i1 = (tab.Cell(2, col).Width / 3) * 2;
int i;
for (i = 1; i < col; i++)
{
i2 += tab.Cell(2, i).Width;
}
i3 = i1 + i2;
return i3;
}
any and all suggestions are welcomed and appreciated,
Greetings
ShadowFlame

At long last I have figured it out, and am eternalising(making eternal?) for future reference and documentation purposes :-).
What I didn't do previously was: add an anchor range to the shape.
Where did I find this: nowhere, had to figure it out by using the MSDN documentation that shapes are linked to anchors, and that if you do not specify, c# will put your shapes on a default range(first page in my case)
Solution:(Please note, this is an extract of code, assuming some knowledge of word interop. If any more information is required, do not hesitate to ask.)
//create word document
--some code
//insert page break here
--some code
//insert paragraph
--some code
//insert table here
--some code
//define range
Word.Range shapeAnchor = doc.Bookmarks.get_Item(ref oEndOfDoc).Range;
//insert shape
Microsoft.Office.Core.MsoAutoShapeType sh =
Microsoft.Office.Core.MsoAutoShapeType.msoShapeUpArrow;
Word.Shape shh = doc.Shapes.AddShape(sh.GetHashCode(), x, y,
width, height, shapeAnchor);
The code that is actually written out can be put into a function, which will then work always.
Greetings,
ShadowFlame

Related

Combine BarChart and PointChart

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..)

Accessing the Y value of a line chart based on cursor position

I'm new to charts and have a line chart that looks as thus.
The vertical line is where the cursor is and updates via mousemove rather than mouseclick.
As the title suggests what I want to do is access the Y value at the point at which the vertical line and the 'data line' intersect.
I've tried this - Get Y value of series from the X Cursor position where a ChartArea is clicked on but unless I'm missing something it just doesn't work, it either returns the first or the last value in the series depending on which datapoint you use.
I've tried hittestresult, that only seems to work if you're 'touching' the data line itself.
Any ideas?
Since you didn't show us any code and didn't answer my questions I can only assume that your chart doesn't have valid, i.e. numeric x-values.
This means the the x-values are all 0 and can't be used for anything: neither for setting a zoom range, not for formatting axis or other labels and also not for finding the DataPoints at an x-position.
This can be called 'implicitly indexed'. The result is similar to explicitly indexed charts, which results from setting the IsXValueIndexed of a Series to true: The DataPoints are lined up in a row and all are displayed at the same distance.
This is usually not what one wants and I really suggest you fix it by adding the DataPoints not like this:
for (int i = 0; i < count; i++) chart1.Series[0].Points.AddY(someYValue);
but maybe like this:
for (int i = 0; i < count; i++) chart1.Series[0].Points.AddXY(i, someYValue);
Then the linked answer will work just fine.
But just to show how you could workaround here is how to find the two closest points in an indexed chart.
Note that it uses a function, (actually two) that calculates the pixel rectangle of the inner plot postion. You can find them here or here..:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
ChartArea ca = chart1.ChartAreas[0];
Series S = chart1.Series[0];
RectangleF rf = InnerPlotPositionClientRectangle(chart1, ca);
float px = (float)( (e.X - rf.X) * S.Points.Count / rf.Width );
int p0 = (int)px; // previous point
int p1 = p0 + 1; // next point
if (p0 >= 0 && p0 < S.Points.Count)
Console.WriteLine( "DataPoint # " + p0 + " has a y-value of " +
S.Points[p0].YValues[0].ToString("0.00"));
//..
}
It will work but you really should correct the way you add the data points!

C# EPPLUS Chart SetPosition Method Row Offset does not work

I am creating a chart to export to excel. I need to create several, so I would offset them using the SetPosition() method with the 4 parameters:
SetPosition(int row, int rowoffset in pixel, int col, int coloffset in pixel)
thus
chart.SetPosition(startRow, 350*i, 0, 50);
The problem is that the second row offset parameter stretches the chart by 350*i pixels higher. This must be a bug since the col offset 4th parameter works fine and as expected.
I need to use startRow to start at a specific row cell in the sheet, so I need to get the row offset to work somehow.
Any ideas?
The RowOffset and ColumnOffset have given me trouble as well and I avoid using them in that function. If you look at the source could you can see it doing alot of match with the chart height/width so it seems to do more then just set and top/left position. I have not gone through the effort to fully reverse engineer it.
I just do the math myself. Knowing the default row height in pixels is the only thing that you have to watch out for since this can be customized by the user which you cannot know at design time Here is what I do:
using (var pck = new ExcelPackage(fi))
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("Sheet1");
worksheet.Cells.LoadFromDataTable(datatable, true);
//Set specific values to make predictable
const int EXCELCHARTWIDTH = 375;
const int EXCELCHARTHEIGHT = 350;
//Have to assume a standard screen dpi but can be customized by the user
const double EXCELDEFAULTROWHEIGHT = 20.0;
var startCell = (ExcelRangeBase)worksheet.Cells["A1"];
for (var i = 0; i < 10; i++)
{
var chart = worksheet.Drawings.AddChart("chart " + i, eChartType.Pie);
chart.SetSize(EXCELCHARTWIDTH, EXCELCHARTHEIGHT);
chart.SetPosition(startCell.Start.Row, 0, startCell.Start.Column, 0);
var chartcellheight = (int)Math.Ceiling(EXCELCHARTHEIGHT / EXCELDEFAULTROWHEIGHT);
startCell = startCell.Offset(chartcellheight, 0);
}
pck.Save();
}
The offset is an offset within one cell.
So if you have a cell 64 x 20 pixels (default) it should not usually exceed 64 or 20 resp. To set the top left corner of a chart just in the middle of a cell, call:
chart.SetPosition(row , 10, col, 32);
also note that if you read the position from From.RowOff, you need to divide it by 9525

How can I control table column width in Word documents using DocX?

I am trying to recreate a table like this:
I am using the DocX library to manipulate Word files, but I'm having trouble getting the widths right. Trying to set the widths of cells only seems to work when it's not set to the window autofit mode, and it only seems to resize when the specified width is greater than half of the table width, or rather, I can make a cell bigger than half the width but not smaller.
What would be the simplest way to reproduce the intended table?
I found the answer to this myself. In order to properly set the width, you have to loop through each cell in a column and set every width. This will not work with any autofit options selected.
Try this :
Table table = doc.AddTable(2, 2);
table.SetColumnWidth(0, 500);
//first is column index, the second is column width
Bit of an old post to tag to, but after having the same issue it would appear that none of the widths on either the cells or columns actually work, so as a dirty workaround, you can loop through each column and cell adding text to each of the cells, make the text white and finally use the autofit option to autofit to contents eg.
Table t2 = doc.AddTable(2, 8);
for (int i = 0; i < t2.RowCount; i ++)
{
for(int x = 0; x < t2.ColumnCount; x++)
{
t2.Rows[i].Cells[x].Paragraphs.First().Append("12").Color(Color.White);
}
}
t2.AutoFit = AutoFit.Contents;
doc.InsertTable(t2);
This is the way:
Table t = doc.AddTable(1, 5);
t.SetWidthsPercentage(new[] { 20f, 20f, 40f, 10f, 10f }, 500);
The float array sets width percentage for each of the columns, second parameter is the total width of the table.

Displaying array items in an irregular grid format

I have an array of fixed sized images that I want to display in a grid in the following format, with the lines containing seven items centered relative to the line above it.
12345678
1234567
12345678
1234567
I'm unsure as to how I can iterate through the array to achieve this. Any help is appreciated.
You wouldn't be able to accomplish this in the console, because the text is rendered without any styling information. Without a space in front, it looks like you wrote it. If you put a space before the text, it will look right aligned. There is no such thing as a half-space in the console.
If you were to render it in something more rich, such as html, this would be accomplishable.
Don't use a grid; the data isn't being shown in tabular form. If you're working with ASP.NET, use a server-side repeater with a Panel wrapped around each row, and set the panel's HorizontalAlign="Center" where appropriate.
I'm not familiar with XNA so I don't know if you are blessed with control windows you can place around the screen or have to perform all drawing manually. Irrespective, the centering logic is the same and can be adapted:
As text has to be placed by the coordinate of the top-left corner within the containing box (be it the screen, an enclosing panel or window, &c.) you need to perform the following calculation:
textLeft = (containerWidth - textWidth) / 2
To visualize this, it might be better to expand it:
textLeft = containerWidth / 2 - textWidth / 2
So, starting on the left side of the container (x of zero) you move half way across the container, then move back half the width of the text, thus placing half the text width either side of the centre line.
Here's what I ended up with, using an idea from El Ronnoco
for (int i = 0; i <= 7; i++)
{
for (int j = 0; j <= 7; j++)
{
grid[i, j].posX = i * 50;
grid[i, j].posY = j * 50;
if (i % 2 > 0)
{
grid[i, j].posY += 25;
if (j == 7)
{
//remove grid[i, j] from array/sight
}
}
}
Thank you for all the answers and help.

Categories