I'm working with the System.Windows.Forms.DataVisualization.Charting library of C# in Visual Studio.
Creating the graphs themselves is no problem, however, since I'm using SeriesChartType.StackedArea100 for my serieses (which always fills the vertical graph space 100%), the grid (X & Y) is completely covered by the graphs.
However, I want the X-grid to be above the graphs, so it's easier to see which point belongs to what.
Am I missing something obvious here?
Gridlines are always drawn under the DataPoints.
One option is to make the Colors of the DataPoints semi-transparent.
Here is an example:
chart1.ApplyPaletteColors(); // necessary to access the original colors
if (checkBox1.Checked)
{
foreach (Series s in chart1.Series) s.Color = Color.FromArgb(192, s.Color);
}
You can raise alpha to 224 and still see the lines.
Or you could owner-draw GridLines in one of the xxxPaint events; but that of course is a little more complicated. OK, a lot more..
The drawing itself is regular GDI+ drawing with DrawLine calls in two loops.
But to get the loops and the coordinates right you need to :
Make sure you know/control the Minimum, Maximum & Interval for the axes. If they are not set but still on their auto-values you need to find a way to get at them.
know the Rectangle of the InnerPlotPosition in pixels(!). See here for two functions that will help you !
Here is an example:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (checkBox1.Checked) return;
ChartArea ca = chart1.ChartAreas[0];
RectangleF ipar = InnerPlotPositionClientRectangle(chart1, ca);
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Color gc = ax.MajorGrid.LineColor;
Pen pen = new Pen(gc);
double ix = ax.Interval == 0 ? 1 : ax.Interval; // best make sure to set..
double iy = ay.Interval == 0 ? 50 : ay.Interval; // ..the intervals!
for (double vx = ax.Minimum; vx <= ax.Maximum; vx+= ix)
{
int x = (int)ax.ValueToPixelPosition(vx) + 1;
e.ChartGraphics.Graphics.DrawLine(pen, x, ipar.Top, x, ipar.Bottom);
}
for (double vy = ay.Minimum; vy <= ay.Maximum; vy += iy)
{
int y = (int)ay.ValueToPixelPosition(vy) + 1;
e.ChartGraphics.Graphics.DrawLine(pen, ipar.Left, y, ipar.Right, y);
}
pen.Dispose();
}
You should disable the GridLines and maybe even make the the Axes transparent:
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisX.LineColor = Color.Transparent;
chart1.ChartAreas[0].AxisY.LineColor = Color.Transparent;
Related
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 have a 6300 * 5 array with:
Columns 1,2 = CIE data
Columns 3,4,5 = S R G B
How should I Draw this in MsChart?
You have several options:
Add DataPoints with Markers in the respective Colors
Add Annotations
Use one of the xxxPaint events
With only 6500 points you can't really fill the area by setting single pixels. So you better use a FillElipse call for each point.
If you use the Pre- or PostPaint event you will need to use the AxisX/Y methods ValueToPixelPosition for calculating the pixel coordinates from the CIE values.
In any case you set the Minimum and Maximum for both Axes.
Also you will need to calculate either the Markers' or the Annotations' or the ellipses' size from the chart's ClientSize to avoid ugly gaps in the colored area.
If you want to use DataPoints set the ChartType = Point and use this function for each of your data:
DataPoint Cie2DataPoint(float x, float y, float r, float g, float b)
{
var dp = new DataPoint(x, y);
dp.Color = Color.FromArgb((int)(256 * r), (int)(256 * g),(int)(256 * b));
dp.MarkerColor = dp.Color;
return dp;
}
Here are examples of helper function:
int MarkerSize(Chart chart, int count)
{
return Math.Max(chart.ClientSize.Width, chart.ClientSize.Height )/ count + 1
}
void Rescale(Chart chart)
{
Series s = chart3.Series[0];
s.MarkerSize = MarkerSize(chart3, (int)Math.Sqrt(s.Points.Count));
}
The former takes an estimate of how many plot points you expect per axis; you may need to experiment a little. The next one assumes the points are actually filling a square; also that you only have one ChartArea.
This should also be modified for your data!
We need to rescale the sizes when the Chart is resized:
private void chart3_Resize(object sender, EventArgs e)
{
Rescale (sender as Chart);
}
Here is an example of setting it up with a calculated set of data. You should loop over your list of data instead..:
Series s = chart3.Series[0];
s.ChartType = SeriesChartType.Point;
s.MarkerSize = 3;
for (int x = 0; x < 100; x++)
for (int y = 0; y < 100; y++)
{
s.Points.Add(Cie2DataPoint(x/100f, y/100f, x/100f, y/100f, (x+y)/200f));
}
ChartArea ca = chart3.ChartAreas[0];
ca.AxisX.Minimum = 0;
ca.AxisY.Minimum = 0;
ca.AxisX.Maximum = 1;
ca.AxisY.Maximum = 1;
ca.AxisX.Interval = 0.1f;
ca.AxisY.Interval = 0.1f;
ca.AxisX.LabelStyle.Format = "0.00";
ca.AxisY.LabelStyle.Format = "0.00";
Rescale(chart3);
Result:
After grabbing ~6k colors from a CIE color chart the result looks rather grainy but basically correct:
Note that you probably need to allow for the reversed y-axis somehow; I simply subtracted my y-values from 0.9f. Use your own numbers!
I'm working with MS Charts, and somehow I can't figure out a way to draw the points on top of the y-axis.
As you can see in the image below, the points at x = 0 (label 1998) are below the Y-axis.
Does anyone recognize this problem? Has it something to do with order of prepaint and postpaint events?
EDIT: Test with custom drawn dots, only until the y-axis..
In a Chart all DataPoints go over the GridLines but under the Axes and the TickMarks.
To paint them over the Axes as well you need to owner draw them in one of the Paint events.
This is simple if your ChartType is of type Points, Spline or Line and your MarkerType of Circle or Rectangle.
For most other types like Column or Bar etc this is a lot harder.
Here is a simple example for Points; pick a size you like better..!
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (chart1.Series.Count == 0) return;
Axis AX = chart1.ChartAreas[0].AxisX;
Axis AY = chart1.ChartAreas[0].AxisY;
chart1.ApplyPaletteColors();
foreach (Series s in chart1.Series)
foreach (DataPoint dp in s.Points)
{
float x = (float )AX.ValueToPixelPosition(dp.XValue);
float y = (float )AY.ValueToPixelPosition(dp.YValues[0]);
float w2 = 3.6f;
using (SolidBrush brush = new SolidBrush(Color.YellowGreen))
e.ChartGraphics.Graphics.FillEllipse(brush,
x - w2, y - w2, w2 * 2f, w2 * 2f);
}
}
I have suppressed the custom drawing for some points.
So I'm writing a program that generates a chart and saves it to PNG. From what I've read, if I were drawing to a window, it doesn't behave this way, but I'm not doing that.
The problem is that when I pass the brush I use to draw the label to another method to do the drawing, sometimes the text comes out looking bold. Also, the Y coordinate seems to have something to do with it, since it happens on every other row of the chart I'm drawing. And it's not a nice bold, either, it's like a gritty, messy looking bold. Some people have suggested changing the text rendering hint to antialiased, and it solves the "bolding" problem, but it doesn't look as nice as ClearType.
Note that none of this happens if I do everything in one method without passing the brush around, which is the most puzzling part of this. Any ideas?
Here's some of the code:
// Draw the timeline.
int y = 0;
bool shadeRow = true;
foreach (TimelineRow row in timeline.chart)
{
int rowHeight = row.height + TimelineRow.ROW_GAP;
if (shadeRow)
{
g.FillRectangle(shadeBrush, 0, y, chartWidth, rowHeight);
}
// Draw name labels, guidelines, and timeline row.
g.DrawString(row.name, labelFont, labelBrush, PADDING, (int)Math.Ceiling(y + (float)PADDING / 2));
for (int i = 0; i < row.years.Length; i++)
{
int blockX = labelsWidth + i * TimelineRow.DEFAULT_HEIGHT;
g.DrawLine(i % 5 == 0 ? yearGridDark : yearGridLight, blockX, y, blockX, y + rowHeight);
}
DrawRow(row, g, labelsWidth, y + 8);
y += rowHeight;
shadeRow = !shadeRow;
}
// Draw the year labels
int x = labelsWidth;
for (int year = timeline.startYear; year <= timeline.endYear; year += 5)
{
string yearString = Convert.ToString(year);
int width = (int)g.MeasureString(yearString, labelFont).Width;
g.DrawString(yearString, labelFont, labelBrush, x - width / 2, y);
x += 5 * TimelineRow.DEFAULT_HEIGHT;
}
I've had similar issues with drawing strings. In my cases, clearing the image FIRST with the background color has fixed the problem.
Wow, that actually did it.
Use Graphics.Clear() to set the initial color:
Bitamp bmp = new Bitmap(...);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
// ... now draw with "g" ...
I've been racking my brain trying to figure out how to animate an effect. This is related to a question I asked on math.stackexchange.com.
https://math.stackexchange.com/questions/91120/equal-division-of-rectangles-to-make-total/
As a side note, I didn't implement the drawing algorithm that was defined on the question above -- instead using my own in order to change the perspective to make it look more condensed.
I've been able to draw a stationary 3d style effect, but I am having trouble wrapping my brain around the logic to make the lines below look like they are coming towards you.
My code is as follows,
List<double> sizes = new List<double>();
private void Form1_Load(object sender, EventArgs e)
{
for (int y = 1; y < 10; y++)
{
double s = ((240 / 2) / y) / 4;
sizes.Add(s);
}
sizes.Add(0);
}
int offset = 0;
private void button1_Click(object sender, EventArgs e)
{
Bitmap b = new Bitmap(320, 480);
Graphics g = Graphics.FromImage(b);
Color firstColor = Color.DarkGray;
Color secondColor = Color.Gray;
Color c = firstColor;
int yOffset = 0;
for(int i = 0; i < sizes.Count; i++)
{
c = (i % 2 == 0) ? firstColor : secondColor;
int y = (int)Math.Round(b.Height - yOffset - sizes[i]);
int height = (int)Math.Round(sizes[i]);
g.FillRectangle(new SolidBrush(c), new Rectangle(0, y + offset, b.Width, height + offset));
yOffset += (int)sizes[i];
}
this.BackgroundImage = b;
offset+=1;
}
Each button click should cause the rectangles to resize and move closer. However, my rectangles aren't growing as they should. My logic draws fine, but simply doesn't work as far as moving goes.
So my question is:
Is there an existing algorithm for this effect that I am not aware of, or is this something pretty simple that I'm over thinking? Any help in correcting my logic or pointing me in the right direction would be very appreciated.
Interesting...
(video of the answer here: http://youtu.be/estq62yz7v0)
I would do it like that:
First, drop all RECTANGLE drawing and draw your effect line by line. Like so:
for (int y=start;y<end;y++)
{
color = DetermineColorFor(y-start);
DrawLine(left, y, right, y, color);
}
This is of course pseudo-code not to be troubled with GDI+ or something.
Everything is clear here, except on how to code DetermineColorFor() method. That method will have to return color of the line at specified PROJECTED height.
Now, on the picture, you have:
you point of view (X) - didn't know how to draw an eye
red line (that's your screen - projection plane)
your background (alternating stripes at the bottom)
and few projecting lines that should help you devise the DetermineColorFor() method
Hint - use triangle similarity to go from screen coordinates to 'bar' coordinates.
Next hint - when you are in 'bar' coordinates, use modulo operator to determine color.
I'll add more hints if needed, but it would be great if you solved this on your own.
I was somehow inspired by the question, and have created a code for the solution. Here it is:
int _offset = 0;
double period = 20.0;
private void timer1_Tick(object sender, EventArgs e)
{
for (int y = Height / 3; y < Height; y++)
{
using (Graphics g = CreateGraphics())
{
Pen p = new Pen(GetColorFor(y - Height / 3));
g.DrawLine(p, 0, y, Width, y);
p.Dispose();
}
}
_offset++;
}
private Color GetColorFor(int y)
{
double d = 10.0;
double h = 20.0;
double z = 0.0;
if (y != 0)
{
z = d * h / (double)y + _offset;
}
double l = 128 + 127 * Math.Sin(z * 2.0 * Math.PI / period);
return Color.FromArgb((int)l, (int)l, (int)l);
}
Experiment with:
d - distance from the eye to the projection screen
h - height of the eye from the 'bar'
period - stripe width on the 'bar'
I had a timer on the form and event properly hooked. Timer duration was 20ms.
Considering that you're talking here about 2D rendering, as much as I understodd, to me it seems that you're gonna to reenvent the wheel. Cause what you need, IMHO; is use Matrix Transformations already available in GDI+ for 2D rendering.
Example of aplying it in GDI+ : GDI+ and MatrixTranformations
For this they use System.Drawing.Drawing2D.Matrix class, which is inside Graphics.
The best ever 2D rendering framework I ever used is Piccolo2D framework which I used with great success in big real production project. Definitely use this for your 2D rendering projects, but first you need to study it little bit.
Hope this helps.