Im wondering if its possible to add colored triangles (pointing up or down) at specific points (x,y) on an existing chart, such that they will update their positions if the chart is scrolled/zoomed.
Ive had a google around but havent stumbled across anything which seems to meet those requirements.
Any pointers would be appreciated.
Draw a point chart with markers.
Create your own image and use MarkerImage property to load custom image
Answer Explaining how to get your inverted triangles
Custom MarkerStyles possible in Microsoft Chart Controls?
As described inside the MS Chart Samples, you could probably use annotations for this
private void AddLineAnnotation()
{
LineAnnotation annotation = new LineAnnotation();
annotation.AnchorDataPoint = Chart1.Series[0].Points[2];
annotation.Height = -25;
annotation.Width = -25;
annotation.LineWidth = 2;
annotation.StartCap = LineAnchorCapStyle.Arrow;
annotation.EndCap = LineAnchorCapStyle.Arrow;
Chart1.Annotations.Add(annotation);
}
You can even define a custom polygon as annotation
private void AddPolygonAnnotation()
{
PolygonAnnotation annotation = new PolygonAnnotation();
annotation.AnchorDataPoint = Chart1.Series[0].Points[2];
// explicitly set the relative height and width
annotation.Height = 50;
annotation.Width = 30;
annotation.BackColor = Color.FromArgb(128, Color.Orange);
annotation.LineColor = Color.Black;
annotation.LineDashStyle = ChartDashStyle.Solid;
// define relative value points for a polygon
PointF [] points = new PointF[5];
points[0].X = 0;
points[0].Y = 0;
points[1].X = 100;
points[1].Y = 0;
points[2].X = 100;
points[2].Y = 100;
points[3].X = 0;
points[3].Y = 100;
points[4].X = 50;
points[4].Y = 50;
annotation.Path.AddPolygon(points);
Chart1.Annotations.Add(annotation);
}
Related
This is my first project in c# and I'm trying to create plots from data.
I'm struggling with drawing minor and major grid lines and labels on a logarithmic scale.
I've set the scale to logarithmic, set the base to 10 and both major and minor intervals to 1, and it works great, however, the interval starts with the minimum value on scale, so for example if data starts at 30M (I'm dealing with frequencies) the next major tick is at 300M and 3G, which is not as it should be.
Is there a way to set major grid to 1, 10, 100 etc, independent of what data is displayed? i've tried changing intervals, base and offset but have not achieved much.
area.AxisX.IsLogarithmic = true;
area.AxisX.LogarithmBase = 10;
area.AxisX.Interval = 1;
//area.AxisX.IntervalOffset = 10000;
area.AxisX.IntervalAutoMode = IntervalAutoMode.FixedCount;
area.AxisX.MajorGrid.Enabled = true;
area.AxisX.MajorTickMark.Enabled = true;
area.AxisX.MinorGrid.Enabled = true;
area.AxisX.MinorGrid.Interval = 1;
area.AxisX.MinorTickMark.Enabled = true;
area.AxisX.MinorTickMark.Interval = 1;
area.AxisX.Minimum = minMaxXY[0]; // in this example 30 M
area.AxisX.Maximum = minMaxXY[1]; // in this example 1 G
here's the link to the current grid
https://ibb.co/3WkxLfc
Thank you for your time and answers!
Thanks to TaW replay I managed to get my program working.
Here is my solution using customLabels location to draw the grid lines.
private void Chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (e.Chart.ChartAreas.Count > 0) // I don't yet truly understand when this event occurs,
// so I got plenty of null references.
{
Graphics g = e.ChartGraphics.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
Color minorGridColor = Color.Gainsboro;
ChartArea area = e.Chart.ChartAreas[0];
double aymin = area.AxisY.Minimum;
double aymax = area.AxisY.Maximum;
int y0 = (int)area.AxisY.ValueToPixelPosition(aymin);
int y1 = (int)area.AxisY.ValueToPixelPosition(aymax);
foreach (var label in chart1.ChartAreas[0].AxisX.CustomLabels)
{
double xposition = area.AxisX.ValueToPixelPosition(Math.Pow(10, label.FromPosition + 0.1));
if (xposition > area.AxisX.ValueToPixelPosition(minMaxXY[0]) && xposition < area.AxisX.ValueToPixelPosition(minMaxXY[1]))
//this prevents drawing of lines outside of the chart area
{
int x = (int)xposition;
using (Pen dashed_pen = new Pen(Color.FromArgb(10, 0, 0, 0), 1))
{
dashed_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
g.DrawLine(dashed_pen, x, y0, x, y1);
}
}
}
}
}
I also found the CustomLabel.GridTicks Property, but for some reason it did not work.
I'm currently working on a project, in which I need to create a Polar Plot with dynamically generated data. I've managed to create a somewhat decent polar plot, but have not been able to create what is needed.
This is my Polar Plot
this is the code I used to set the offset in the middle:
public Form1()
{
InitializeComponent();
chart1.ChartAreas[0].AxisX.MajorTickMark.Enabled = false;
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.Minimum = -20;
chart1.ChartAreas[0].AxisY.MajorGrid.IntervalOffset = 15;
chart1.ChartAreas[0].AxisY.MajorGrid.Interval = 5;
chart1.ChartAreas[0].AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Solid;
chart1.ChartAreas[0].AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Solid;
}
I found some help here: How to displace the origin of the Y axis on a polar Mschart?
I got a example on how I'm trying to get the polar:
The finished example
I don't think you can make an axis start from anywhere but its minimum.
(The linked post only makes the labels start from a different value.)
So we'll have to help with a little bit of owner-drawing.
A few short references:
var ca = chart1.ChartAreas[0];
var ax = ca.AxisX;
var ay = ca.AxisY;
Now let's hide the y-axis:
ay.LineWidth = 0;
To draw the portion of the axis from the interval offset to the maximum we simply code the PostPaint event:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
// add references..
..
// then use values to calulate pixel coordinates..
int py1 = (int)ay.ValueToPixelPosition(ay.Minimum + ay.IntervalOffset);
int py2 = (int)ay.ValueToPixelPosition(ay.Maximum);
int px = (int)ax.ValueToPixelPosition(ax.Maximum - ax.Minimum);
// blue to make it stand out
e.ChartGraphics.Graphics.DrawLine(Pens.Blue, px, py1, px, py2);
}
Result:
Of course finding the right values for Interval, IntervalOffset, Minimum and Maximum is all up to you..
Update: If you want to have a full set of shortened x-axis gridlines you could do a lot of math or use a graphics transform. As usual the latter is so much easier..:
Graphics g = e.ChartGraphics.Graphics;
int pyc = (int)ay.ValueToPixelPosition(ay.Minimum); // y-center
for (int i = 0; i < 360 / ax.Interval; i++)
{
g.TranslateTransform(px, pyc);
g.RotateTransform((float)(i * ax.Interval));
g.TranslateTransform(-px, -pyc);
g.DrawLine(Pens.colorOfYourChoice, px, py1, px, py2);
g.ResetTransform();
}
After setting ax.Interval = 30; we get this result:
I'm making a program to graph parabolas, and I want to make the X and Y axes (the ones from (0,0)) a different color. I haven't found any options to do so, and the only solution I've found is to make a large grid and set its increment to half the graph's size. Is there an alternative?
I used the default chart control. I'd expect something like:
You can set Crossing for axis to move the axis to center of chart. Also you can set LineWidth for axis to make it thicker. Also you can set ArrowStyle to have an arrow at the end of axis.
For example, to have a chart like this:
Use such code:
private void Form1_Load(object sender, EventArgs e)
{
//Set Chart Margins
this.chart1.ChartAreas[0].Position.Auto = false;
this.chart1.ChartAreas[0].Position.X = 10;
this.chart1.ChartAreas[0].Position.Y = 10;
this.chart1.ChartAreas[0].Position.Width = 80;
this.chart1.ChartAreas[0].Position.Height = 80;
//Configure X Axis
this.chart1.ChartAreas[0].AxisX.Crossing = 0;
this.chart1.ChartAreas[0].AxisX.Interval = 1;
this.chart1.ChartAreas[0].AxisX.LabelStyle.Enabled = false;
this.chart1.ChartAreas[0].AxisX.LineWidth = 2;
this.chart1.ChartAreas[0].AxisX.ArrowStyle =
System.Windows.Forms.DataVisualization.Charting.AxisArrowStyle.Lines;
//Configure Y Axis
this.chart1.ChartAreas[0].AxisY.Crossing = 0;
this.chart1.ChartAreas[0].AxisY.Interval = 5;
this.chart1.ChartAreas[0].AxisY.LineWidth = 2;
this.chart1.ChartAreas[0].AxisY.LabelStyle.Enabled = false;
this.chart1.ChartAreas[0].AxisY.ArrowStyle =
System.Windows.Forms.DataVisualization.Charting.AxisArrowStyle.Lines;
//Set Chart Type
this.chart1.Series[0].ChartType =
System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline;
//Set Data
var p = new List<PointF>();
for (int i = -5; i <= 5; i++)
{
p.Add(new PointF(i, i * Math.Abs(i)));
}
this.chart1.DataSource = p;
this.chart1.Series[0].XValueMember = "X";
this.chart1.Series[0].YValueMembers = "Y";
this.chart1.Series[0].IsVisibleInLegend = false;
}
I've spent hours trying to solve this silly problem. I create an histogram with asp chart control. All I want to do is have the xaxis label on the left of the column instead of centered on it. Xaxis lable doesn't seem to have a position property like series do, so I can't figure it out and it's frustrating.
Here's a sample code of the type of graphic I'm talking about to show you what I get approximately:
private void Graphique()
{
// Creating the series
Series series2 = new Series("Series2");
// Setting the Chart Types
series2.ChartType = SeriesChartType.Column;
// Adding some points
series2.Points.AddXY(1492, 12);
series2.Points.AddXY(2984, 0);
series2.Points.AddXY(4476, 1);
series2.Points.AddXY(5968, 2);
series2.Points.AddXY(7460, 2);
series2.Points.AddXY(8952, 12);
series2.Points.AddXY(10444, 4);
series2.Points.AddXY(11936, 3);
series2.Points.AddXY(13428, 3);
series2.Points.AddXY(14920, 5);
series2.Points.AddXY(16412, 1);
Chart3.Series.Add(series2);
Chart3.Width = 600;
Chart3.Height = 600;
// Series visual
series2.YValueMembers = "Frequency";
series2.XValueMember = "RoundedValue";
series2.BorderWidth = 1;
series2.ShadowOffset = 0;
series2.IsXValueIndexed = true;
// Setting the X Axis
Chart3.ChartAreas["ChartArea1"].AxisX.IsMarginVisible = true;
Chart3.ChartAreas["ChartArea1"].AxisX.Interval = 1;
Chart3.ChartAreas["ChartArea1"].AxisX.Maximum = Double.NaN;
Chart3.ChartAreas["ChartArea1"].AxisX.Title = "kbps";
// Setting the Y Axis
Chart3.ChartAreas["ChartArea1"].AxisY.Interval = 2;
Chart3.ChartAreas["ChartArea1"].AxisY.Maximum = Double.NaN;
Chart3.ChartAreas["ChartArea1"].AxisY.Title = "Frequency";
}
Now my real chart looks like this, Actual result
I would like something similar to this website :
Desired layout chart
You see, the x label is on the left, which makes way more sense considering that each column of an histogram represents the frequency of a range of values.....
Any help would be appreciated...
Did you try to add CustomLabels to replace the default ones? For example:
for (int i = 0; i <= 10; i++) {
area.AxisX.CustomLabels.Add(i + 0.5, i + 1.5, i, 0, LabelMarkStyle.None);
}
The first two are for positioning and the third would be the text value of the label.
this is the code I use to do the hitTest:
RayHitTestResult hit = VisualTreeHelper.HitTest(
App.MainWin.Viewport, location) as RayHitTestResult;
This code works when the camera width is small (i.e. objects are big). When I zoom out the camera (i.e. objects become smaller) to certain point, then when I click on the viewport, it shows an error on the above code, and the error details is like this:
System.NotSupportedException was unhandled : Hit testing with a
singular MatrixCamera is not supported.
I googled and found nobody having this error. Any idea how to solve it? Thanks!
The code I used to create / update the MatrixCamera: (They are inspired by the examples in the book "3D Programming for Windows: Three-Dimensional Graphics Programming for the Windows Presentation Foundation" by Charles Petzold, Chapter 7)
//create a new camera, initialize it and attach it to the viewport.
public void initCamera(Point3D position, Point3D AimPoint,
Vector3D upDirection, double farDistance,
double nearDistance, double Width)
{
this._Camera = new MatrixCamera();
this.CameraAimPoint = (Vector3D)AimPoint;
//check and adjust the camera depth if needed
this.CameraZAxis = Point3D.Subtract(position, AimPoint);
this.CameraDepth = CameraZAxis.Length;
this.CameraZAxis.Normalize();
this.CameraPosition = this.CameraAimPoint + (this.CameraZAxis * this.CameraDepth);
this.CameraFarDistance = farDistance;
this.CameraNearDistance = nearDistance;
this.CameraWidth = Width;
this.CameraXAxis = Vector3D.CrossProduct(upDirection, this.CameraZAxis);
this.CameraXAxis.Normalize();
this.CameraYAxis = Vector3D.CrossProduct(this.CameraZAxis, this.CameraXAxis);
this._CameraViewMatrix = new Matrix3D();
this._CameraViewMatrix.M14 = 0;
this._CameraViewMatrix.M24 = 0;
this._CameraViewMatrix.M34 = 0;
this._CameraViewMatrix.M44 = 1;
this.updateViewMatrix();
this._CameraProjectMatrix = new Matrix3D();
this._CameraProjectMatrix.M14 = 0;
this._CameraProjectMatrix.M24 = 0;
this._CameraProjectMatrix.M34 = 0;
this._CameraProjectMatrix.M44 = 1;
this.updateProjectionMatrix();
this._Viewport.Camera = this._Camera;
}
private void updateViewMatrix(bool axisChanged=true)
{
if (axisChanged==true)
{
this._CameraViewMatrix.M11 = this.CameraXAxis.X;
this._CameraViewMatrix.M12 = this.CameraYAxis.X;
this._CameraViewMatrix.M13 = this.CameraZAxis.X;
this._CameraViewMatrix.M21 = this.CameraXAxis.Y;
this._CameraViewMatrix.M22 = this.CameraYAxis.Y;
this._CameraViewMatrix.M23 = this.CameraZAxis.Y;
this._CameraViewMatrix.M31 = this.CameraXAxis.Z;
this._CameraViewMatrix.M32 = this.CameraYAxis.Z;
this._CameraViewMatrix.M33 = this.CameraZAxis.Z;
}
this._CameraViewMatrix.OffsetX = -Vector3D.DotProduct(this.CameraXAxis, this.CameraPosition);
this._CameraViewMatrix.OffsetY = -Vector3D.DotProduct(this.CameraYAxis, this.CameraPosition);
this._CameraViewMatrix.OffsetZ = -Vector3D.DotProduct(this.CameraZAxis, this.CameraPosition);
this._Camera.ViewMatrix = this._CameraViewMatrix;
this._3DTo2DTransformMatrixIsDefined = false;
AfterDraw();
}
//
private void updateProjectionMatrix() {
double ScaleX = 2 / CameraWidth;
double ScaleY = _Viewport.ActualWidth / _Viewport.ActualHeight * ScaleX;
double ScaleZ = 1 / (CameraNearDistance - CameraFarDistance);
double zOffset = CameraNearDistance * ScaleZ;
_CameraProjectMatrix.M11 = ScaleX;
_CameraProjectMatrix.M22 = ScaleY;
_CameraProjectMatrix.M33 = ScaleZ;
_CameraProjectMatrix.OffsetZ = zOffset;
_Camera.ProjectionMatrix = _CameraProjectMatrix;
_3DTo2DTransformMatrixIsDefined = false;
_PixelToWorldUnit = CameraWidth / _Viewport.ActualWidth;
AfterDraw();
}
The code to perform camera zooming:
private void _cameraZoomToScale(double width)
{
if (width< 1) width = 1;
this.CameraWidth = width;
updateProjectionMatrix();
}
It seems pretty clear that you are zooming further than the MatrixCamera supports. Figure out at what zoom level this exception occurs and prevent the user from getting that far. It looks like you might already be trying to do that already with if (scale < 1) scale = 1; but scale doesn't seem to be used elsewhere in the posted code.