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
Related
I am trying to take a grayscale bitmap and extract a single line from it and then graph the gray values. I got something to work, but I'm not really happy with it. It just seems slow and tedious. I am sure someone has a better idea
WriteableBitmap someImg; //camera image
int imgWidth = someImg.PixelWidth;
int imgHeight = someImg.PixelHeight;
Int32Rect rectLine = new Int32Rect(0, imgHeight / 2, imgWidth, 1); //horizontal line half way down the image as a rectangle with height 1
//calculate stride and buffer size
int imgStride = (imgWidth * someImg.Format.BitsPerPixel + 7) / 8; // not sure I understand this part
byte[] buffer = new byte[imgStride * rectLine.Height];
//copy pixels to buffer
someImg.CopyPixels(rectLine, buffer, imgStride, 0);
const int xGraphHeight = 256;
WriteableBitmap xgraph = new WriteableBitmap(imgWidth, xGraphHeight, someImg.DpiX, someImg.DpiY, PixelFormats.Gray8, null);
//loop through pixels
for (int i = 0; i < imgWidth; i++)
{
Int32Rect dot = new Int32Rect(i, buffer[i], 1, 1); //1x1 rectangle
byte[] WhiteDotByte = { 255 }; //white
xgraph.WritePixels(dot, WhiteDotByte, imgStride, 0);//write pixel
}
You can see the image and the plot below the green line. I guess I am having some WPF issues that make it look funny but that's a problem for another post.
I assume the goal is to create a plot of the pixel value intensities of the selected line.
The first approach to consider it to use an actual plotting library. I have used oxyplot, it works fine, but is lacking in some aspects. Unless you have specific performance requirements this will likely be the most flexible approach to take.
If you actually want to render to an image you might be better of using unsafe code to access the pixel values directly. For example:
xgraph.Lock();
for (int y = 0; y < imgHeight; y++){
var rowPtr = (byte*)(xgraph.BackBuffer + y * xgraph.BackBufferStride);
for(int x = 0; x < imgWidth; x++){
rowPtr[x] = (byte)(y < buffer[i] ? 0 : 255);
}
}
self.Unlock(); // this should be placed in a finally statement
This should be faster than writing 1x1 rectangles. It should also write columns instead of single pixels, and that should help making the graph more visible. You might also consider allowing arbitrary image height and scale the comparison value.
If you want to plot the pixel values along an arbitrary line, and not just a horizontal one. You can take equidistant samples along the line, and use bilinear interpolation to sample the image.
I have a Winforms chart in which I have temperature readings arriving and displaying every second. I like the way the chart works automatically handling the display of the values, but I want to change one simple thing.
I want to increase the minimum displayed y axis range, so it displays a range of 20. At the moment it only displays around 5. I have tried a few things:
//(when new data arrives...)
//Does not work, I think because by default, Size is always NaN?
if (chart1.ChartAreas[0].AxisY.ScaleView.Size < 20)
{
chart1.ChartAreas[0].AxisY.ScaleView.Size = 20;
}
None of these work either:
chart1.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSize = 20;
chart1.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = 20;
chart1.ChartAreas[0].AxisY.ScaleView.MinSize = 20;
chart1.ChartAreas[0].AxisY.Minimum //doesn't seem to have any effect
chart1.ChartAreas[0].AxisY.Maximum //doesn't seem to have any effect
I'm sure I've missed something simple. I hope I have anyway.
The 'minimum display range' is not something built-in in the MSChart control.
But you can easily fake it:
Add a dummy Series which contains only two points to make sure the display range will not go below the range of their y-values..:
int rangeMin = -10;
int rangeMax = 20;
sDummy = chart.Series.Add("dummy");
sDummy.Color = Color.Transparent;
sDummy.IsVisibleInLegend = false;
sDummy.ChartType = SeriesChartType.Point;
sDummy.Points.AddXY(0, rangeMin + 1);
sDummy.Points.AddXY(0, rangeMax - 1);
Style your y-axis as you like:
Axis ay = chart.ChartAreas[0].AxisY;
ay.MajorGrid.Interval = 5;
And add one or more data Series:
sData = chart.Series.Add("data");
sData.LegendText = "Temperature";
sData.ChartType = SeriesChartType.Line;
Now as you add data points with a larger range of values the y-axis will grow its display range to accommodate them. And if you remove the larger points it will shrink back, but not below the range needed for the dummy series..:
Note that since the Chart automatically adds some slack I reduce the range on both sides by 1; with other Intervals etc other numbers are needed..
The code to remove the larger values, btw:
var toRemove = sData.Points.Cast<DataPoint>()
.Where(x => x.YValues[0] >= rangeMax).ToList();
foreach (var dp in toRemove) sData.Points.Remove(dp);
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 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.
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