I have a chart control that I use to show sound pressure lines.
So the X axis is
31.5 63 125 250 500 1000 2000 4000 and 8000.
I set the chart logarithmic on and the log base to 10.
But I'm not able to show all these labels on the axis, it shows 31.5 315 and 3150 only.
Tried to put interval to 1 but no luck.
Can anyone help me?
To make CustomLabels show up on your axis you need to create them with at least these three properties:
Text
FromPosition
ToPosition
Here is an example:
private void button4_Click(object sender, EventArgs e)
{
Series S2 = chart1.Series.Add("Series2");
ChartArea CA = chart1.ChartAreas[0];
CA.AxisY.IsLogarithmic = true;
List<double> fr = new List<double>();
for (int i = 3; i < 18; i++ )
{
fr.Add(Math.Pow(2, 1f * i / 2));
}
for (int i = 1; i < fr.Count; i+=2)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = fr[i - 1];
cl.ToPosition = fr[i + 1];
cl.Text = fr[i] + " Hz";
CA.AxisY.CustomLabels.Add(cl);
}
for (int i = 1; i < 60; i++)
{
chart1.Series[0].Points.AddXY(i, Math.Pow(2, i));
chart1.Series[1].Points.AddXY(i, i * i);
}
}
Note that for best precision you should use FromPositions and ToPositions that don't fall on the Labels but right between. So I skip every other step in the list of frequency steps for the displayed Labels and use them instead for their FromPositions and ToPositions.
Related
I have this code right here:
EDIT: Source Code:
private void btnAnimate_Click(object sender, EventArgs e)
{
Bitmap[] circle = new Bitmap[300];
for (int i = 0; i < circle.Length; i++)
{
circle[i] = new Bitmap(260, 266);
}
double r = 25; // radius
double rr = Math.Pow(r, 2); // r^2
int h = 25;
// x value of centre of circle is represented with h and y value is represented with k
for (int k = 25; k <= 100; k += 25) // y value of center moves down 1 pixel every iteration
{
for (int x = -h; x <= h + r; x++)
{
for (int y = -k; y <= k + r; y++)
{
if (Math.Abs(Math.Pow(x - h, 2) + Math.Pow(y - k, 2)) <= rr)
// if: |(x-h)^2 + (y - h)^2| <= r^2, then draw the pixel
{
circle[k - 25].SetPixel(x, y, Color.Red);
}
}
}
}
for (int l = 25; l <= 100; l+= 25)
{
picBox.Image = circle[l - 25];
btnAnimate.Text = "circle" + (l/25);
System.Threading.Thread.Sleep(1000);
}
}
(it use to be k++ but I made it k+= 25 so it would make it easier to trouble shoot, it will only draw 4 circles instead of the original 75.)
and I want it to draw a circle on a picture box (I've tested it and it can do that), but afterwards I want it to sleep for a while, then clear the picture box, draw a new circle only 1 pixel below it and repeat until the circle hits the ground. The problem with this is that the program is sleeping but it isn't showing the action done in between sleeps. i.e it says to show circle 1 then sleep until it reaches circle 4, but it will not show circles 1 through 3 though it will sleep, it will just show the final circle. It's not even just the pic box
you can see that I wrote
btnAnimate.Text = "circle" + (l/25);
System.Threading.Thread.Sleep(1000);
I want it to change the button text to 1, 2, 3, and 4 to count each iteration but it doesn't do that either. it will just show 4 at the end.
What do you think the problem is?
Solution:
double r = 25; // radius
double rr = Math.Pow(r, 2); // r^2
int h = 25;
// x value of centre of circle is represented with h and y value is represented with k
for (int k = 25; k <= 100; k += 25) // y value of center moves down 1 pixel every iteration
{
Bitmap circle = new Bitmap(260, 266);
for (int x = -h; x <= h + r; x++)
{
for (int y = -k; y <= k + r; y++)
{
if (Math.Abs(Math.Pow(x - h, 2) + Math.Pow(y - k, 2)) <= rr)
// if: |(x-h)^2 + (y - h)^2| <= r^2, then draw the pixel
{
circle.SetPixel(x, y, Color.Red);
}
}
}
picBox.Image = circle;
picBox.Update();
System.Threading.Thread.Sleep(100);
}
You have two problems:
You are not clearing the Bitmap circle drawn circle on each iteration, so you are building upon the previous drawn circle, so it looks like a falling circle with trails. You can fix this by defining your Bitmap circle inside of the first for loop, instead of outside.
It is failing to show the new image inbetween Thread sleeps because you are not telling the control to refresh itself visually. Do this by calling picBox.Update() or picBox.Refresh() after you set picBox.Image = circle.
I am creating One Cross Platform Application in Xamarin Forms and try to draw lines from 10 to -10 using below code. But the problem is lines are drawn from 10 to 0 only. Why this is happening I don't have any Idea.
int margin = 20;
int steps = 20;
float start = margin;
float end = width - margin;
float dHeigth = heigth - (margin * 4);
float hStep = dHeigth / Convert.ToSingle(steps);
float textMargin = 30;
// draw the line
for (int i = 10; i >= -10; i--)
{
float xpoint = i * hStep + margin;
if (i.IsOdd())
{
canvas.DrawLine(start + textMargin, xpoint, end, xpoint, LineWhitePaint);
}
else
{
decimal dText = 0;
canvas.DrawLine(start + textMargin, xpoint, end, xpoint, LineGreyPaint);
if (i < 0)
dText = i;
else
dText = (10 - i);
string txt = dText.ToString();
canvas.DrawText(txt, start + margin, xpoint + 15, TextStyleFillPaintX);
}
}
I am attaching screen shot of that
For the positive lines, you are drawing 10 - i, which yields 0 for the first iteration, 2 for the third and so on. Regarding this, you can see, that you are beginning to draw the lines from the middle of the canvas. The tenth iteration will draw the topmost line (the one with the 10). Further lines are drawn, but not on the screen.
You can see this, too, when you are writing xPoint to the debug output. As i gets negative, xPoint will, too. To fix this, you'll have to offset xPoint to always draw on screen
float xpoint = i * hStep + margin + steps / 2 * hStep;
Alternatively, you could loop from 20 to 0 and change how the text is generated.
for (int i = 20; i >= 0; i--)
{
var xPoint = i * hStep + margin;
// ...
var displayedText = GetDisplayedText(i, steps);
// ...
}
string GetDisplayedText(int i, int steps)
{
var displayedValue = i > steps / 2
? steps - i
: -i - steps / 2; // I think this should be the formula
return displayedValue.ToString();
}
Remarks: It would even better to encapsulate the concept of the lines, to separate their calculation from draawing them. You could create a factory that generates the correct line based on the index and the number of steps and then only iterate over the Line objects, and draw them by passing the canvas. This would make your code way cleaner and neater.
UPDATE
Since we have been able to clarify the requirements, I will give another shot.
First of all, I'd define methods to transform graph coordinates to canvas coordinates
private SKPoint ToCanvasCoordinates(SKPoint graphCoordinates)
{
var x = Margin + TextMargin + (_canvas.Width - 2*Margin - TextMargin)*graphCoordinates.X;
var y = (MaxY - graphCoordinates.Y)*(_canvas.Height - 2 * Margin)/(MaxY - MinY) + Margin;
return new SKPoint(x,y);
}
private SKPoint GetLegendCoordinates(int i)
{
var x = Margin;
var y = (MaxY - graphCoordinates.Y)*(_canvas.Height - 2 * Margin)/(MaxY - MinY) + Margin + 15;
return new SKPoint(x,y);
}
_canvas is a private member field in this case, Margin, MaxY and MinY are properties. I've assumed the min of x being 0 and the max bein 1.
Now you can draw your lines like
for(int i = -1; i <= 10; i++)
{
var lineStart = ToCanvasCoordinates(new SKPoint(0, i));
var lineEnd = ToCanvasCoordinates(new SKPoint(1, i));
canvas.DrawLine(lineStart, lineEnd, LineGreyPaint);
var textPosition = GetLegendCoordinates(i);
canvas.DrawText(i.ToString(), textPosition, TextStyleFillPaintX);
}
Furthermore, if you'd like to draw a line between two of the grid lines, you can use the following methods
private void DrawDataLine(SKPoint start, SKPoint end, SKPaint paint)
{
var startTransformed = ToCanvasCoordinates(start);
var endTransformed = ToCanvasCoordinates(end);
_canvas.DrawLine(startTransformed, endTransformed, paint);
}
private void DrawData(SKPaint paint)
{
for(int i=1; i<_data.Length; i++)
{
DrawDataLine(new SKPoint(data[i-1].X, data[i-1].Y), new SKPoint(data[i].X, data[i].Y)); // given that the objects in _data have the properties X and Y
}
}
I have following issue:
I need to show multiple columns in a chart control (about seven series in one chart area). Now when I have a chart of type "Column" all seven columns get shown side by side. What I want to do is to overlap them. Is this possible?
The following two solutions didn't help me:
Plotting overlapping column or bar chart
Chart control two data set bars overlapping
Thank you.
There is no built-in way to do that.
One workaround is to turn on 3-d, but that will completely change the look of the chart..
The other is to owner-draw the chart.
This is not exactly easy for column and bar types, since the sizeof the columns is not exposed.
Also note that overlapping columns do get somewhat harder to read, esp. when you also have Labels.
Here is an example of a owner-drawing column chart. It has several simplifications:
All Series have the same number of points and are aligned, all y-values are positive and there are no other adornments. They may all be overcome, but probably with some extra efforts..
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (!checkBox2.Checked) return;
int sMax = chart1.Series.Count;
ChartArea ca = chart1.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
float py0 = (float)ay.ValueToPixelPosition(ay.Minimum);
Rectangle ipr = Rectangle.Round(InnerPlotPositionClientRectangle(chart1, ca));
int pMax = chart1.Series[0].Points.Count;
float shift = (overlap * sMax) / 2f;
float deltaX = 1f * ipr.Width / (pMax+1);
float colWidth = 1f * deltaX / sMax;
for (int j = 0; j < chart1.Series.Count; j++)
for (int i = 0; i < chart1.Series[j].Points.Count; i++)
{
DataPoint dp = chart1.Series[j].Points[i];
float px = (float)ax.ValueToPixelPosition(dp.XValue);
float py = (float)ay.ValueToPixelPosition(dp.YValues[0]);
using (SolidBrush brush = new SolidBrush(chart1.Series[j].Color))
e.ChartGraphics.Graphics.FillRectangle(brush,
px + j * colWidth - deltaX / 2 - overlap * j + shift, py,
colWidth, py0 - py );
}
}
It makes use of a function InnerPlotPositionClientRectangle which you can find here
Here is the result:
Note that to access the Series Colors you need to apply them to the Chart:
chart1.ApplyPaletteColors();
The Column width is set like this:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
for (int j = 0; j < chart1.Series.Count; j++)
chart1.Series[j]["PointWidth"] = numericUpDown1.Value.ToString();
}
At "0" the columns disappear.
Column Series has a CustomProperties named DrawSideBySide, set it to False will result columns drawing overlap.
series1.CustomProperties = "DrawSideBySide=False";
It can also be set in IDE, by going to Properties window, Series Collection Editor, then find CustomProperties, DrawSideBySide.
What i want is to set borders between two series in StackedBar Like this image The bold black line between blue and green
I can not figure out any idea to specify the border, i tried to set the borders to the series throuh this code
chart.Series["series0"].BorderWidth = 2;
chart.Series["series0"].BorderColor = Color.Black;
chart.Series["series0"].BorderDashStyle = ChartDashStyle.Solid;
but this the result i got
Here's my code
double l = Convert.ToDouble(query1[i - 1][0]) - 10;
string n = query1[i - 1][1];
int count = 0;
for (double t = l; t < l + 10; t++)
{
//Next line Calc. the occurence of character in a text file
count = n.Split('C').Length - 1;
//Multiple the occurence by 10 so it become percent
chart.Series["series0"].Points.AddXY(t, count * 10);
chart.Series["series0"]["PointWidth"] = "1";
chart.Series["series0"].BorderWidth = 2;
chart.Series["series0"].BorderColor = Color.Black;
chart.Series["series0"].BorderDashStyle = ChartDashStyle.Solid;
count = n.Split('o').Length - 1;
chart.Series["series1"].Points.AddXY(t, count * 10);
chart.Series["series1"]["PointWidth"] = "1";
}
How to achieve the first pic effect using StackedBar ? , if i can not using StackedBar, what chart type you suggest to use ??
There are no built-in chart elements that could easily be made into a borderline between those two Series. (Creating LineAnnotations to achieve this would be a nightmare..)
So the way to add the lines is to draw them onto the surface of the Chart. This is most naturally done in the PostPaint event, provided just for such adornments.
Here the Axes have handy functions to convert between the data values and the pixel positions. We need the ValueToPixelPosition method.
I will take you through variations of Chart drawing that gradually get a little more complicated as we approach the final version..:
Let's start with a simple example: Let's build and adorn a StackedArea chart; here is the drawing code:
private void chart2_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s = chart1.Series[0];
ChartArea ca = chart1.ChartAreas[0];
var pp = s.Points.Select(x=>
new PointF( (float)ca.AxisX.ValueToPixelPosition(x.XValue),
(float)ca.AxisY.ValueToPixelPosition(x.YValues[0]) ) );
if (s.Points.Count > 1)
using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
e.ChartGraphics.Graphics.DrawLines(pen, pp.ToArray());
}
The Points.Select is really just a shorthand for a loop; so after creating the pixel point list we simply draw it.
Now, as you can see, as StackedArea chart is pointy and doesn't look like a StackedBar or StackedColumn chart. So let's cheat and 'rectify' the area chart by adding a few extra points:
void rectifyArea(Series s)
{
for (int i = s.Points.Count - 1; i > 0; i--)
s.Points.InsertXY(i, i - 1, s.Points[i].YValues[0]);
}
Results:
Now that was not so hard; unfortunately you just can't turn a StackedArea to go from left to right instead of bottom-up. So we need to change the chart type to a Bar type eventually..
Here the challenge is to find the right upper and lower corners of those bars. We do have the DataPoint values, but these are in the middle of the bars. So we need to add/subtract half of the Bars' width to get the corners. For this we need the width.
While you have set it with the PointWidth property to 1, what we really need is the pixel width. We best get it by subtracting the pixel coordinates of two neighbouring points.
This makes the PostPaint event a little longer, but still not overly complicated; we will start with a StackedColumn chart, adding two corner points for each data point:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s = chart1.Series[0];
ChartArea ca = chart1.ChartAreas[0];
if (s.Points.Count <= 0) return;
// calculate width of a column:
int pp1 = (int)ca.AxisX.ValueToPixelPosition(s.Points[0].XValue);
int pp2 = (int)ca.AxisX.ValueToPixelPosition(s.Points[1].XValue);
float w2 = Math.Abs(pp2 - pp1) / 2f;
List<PointF> points = new List<PointF>();
for (int i = 0; i < s.Points.Count; i++)
{
DataPoint dp = s.Points[i];
points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) - w2,
(int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) + w2,
(int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
}
if (points.Count > 1)
using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
}
Now this looks pretty much identical to our fake version of the 'rectified area chart'. What will we need to change to apply this to a StackedBar chart? Almost nothing! The only two things we need to take care of are
the direction of the y-axis. Since the points move upward but the pixel coordinates of GDI+ graphhics move downwards we need to create the two cornerpoints in the reverse order.
And we need to reverse the x- and y coodinates, as the axes are reversed for all types of Bar charts.
Here are the two stacked charts with a border:
This is the loop for the StackBar chart:
for (int i = 0; i < s.Points.Count; i++)
{
points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
(float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) + w2));
points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
(float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) - w2));
}
Note that I am drawing with a fixed pen width of 4 pixels. To make it scale with the Chart you may want to calculate the pen width dynamically..
Update
To draw borders on top of several series you can put the code into a loop like this:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Chart chart = chart1;
Series s0 = chart.Series[0];
ChartArea ca = chart.ChartAreas[0];
// calculate width of a bar:
int pp1 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[0].XValue);
int pp2 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[1].XValue);
float delta = Math.Abs(pp2 - pp1) / 2f;
for (int s = 0; s < chart.Series.Count; s++)
{
List<PointF> points = new List<PointF>();
for (int p = 0; p < chart.Series[s].Points.Count; p++)
{
DataPoint dp = chart.Series[s].Points[p];
double v = GetStackTopValue(chart, s, p);
points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
(float)ca.AxisX.ValueToPixelPosition(dp.XValue) + delta));
points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
(float)ca.AxisX.ValueToPixelPosition(dp.XValue) - delta));
}
using (Pen pen = new Pen(Color.DarkOliveGreen, 3f))
e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
}
}
double GetStackTopValue(Chart chart, int series, int point)
{
double v = 0;
for (int i = 0; i < series + 1; i++)
v += chart.Series[i].Points[point].YValues[0];
return v;
}
I want to draw a street with a panel but it wont work. I want to have it with a loop but i cant get it done. The wall and roof must become 20% smaller than the house on the left.
My code:
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
HuisTekenen();
}
}
private void HuisTekenen()
{
gebouw();
dak();
}
private void gebouw()
{
Graphics paper;
paper = panel1.CreateGraphics();
Pen pennetje = new Pen(Color.Green);
int b = 100;
int aantal = 0;
for (int i = 10; aantal <= 5; i += 120)
{
paper.DrawRectangle(pennetje, i, 100, b, 150);
aantal++;
i = i / 100 * 80;
b = b / 100 * 80;
}
}
private void dak()
{
Graphics paper;
paper = panel1.CreateGraphics();
Pen pennetje = new Pen(Color.Red);
int b = 100;
int aantal = 0;
for (int i = 10; aantal <= 5; i+=120)
{
paper.DrawLine(pennetje, i, 10 / 100 * 80, i, b);
paper.DrawLine(pennetje, i, 10 / 100 * 80, i + 100, b);
aantal++;
i = i / 100 * 80;
b = 100 / 100 * 80;
}
}
The result i want to get:
Result i get:
Can you help me?
Thanks!
Here's a way to get started with the box. Youll have to finish your homework on your own.
float size = 50;
float xpos = 0;
float ypos = 0;
for(int i=0;i<5;i++) //whatever you do, this must be what your loop looks like. anything else is going way off in the wrong direction
{
paper.DrawRectangle(pennetje, xpos, ypos, size, size);
xpos += size + 20;
size *= .8f;
}
First a remark: your for-statements are odd, usually the three parts will use the same variable. It can work this way, but doing it like this is certainly not recommended.
A different kind of issue will be this statement:
i = i / 100 * 80;
With variable i being an int, and / performing integer division, the first part i / 100 will produce 0 because that is the int value closest to the real/float result. And of course multiplying 0 by 80 will not do anything.
I suggest you now try again :-)