问题
I need a way to draw a Dictionary<int,int>
into a console application like
Dictionary<int, int> chartList = new Dictionary<int, int>()
{
{50,31}, // x = 50, y = 31
{71,87},
{25,66},
{94,15},
{33,94}
};
DrawChart(chartList);
should result in something like
I've come this far but i'm stuck at the IsHit
method, which determines if at the current coordinates should be set a point or not. Could anyone help me at this point? It returns always true.
public static void DrawChart(Dictionary<int, int> dict)
{
int consoleWidth = 78;
int consoleHeight = 20;
Console.WriteLine(dict.Max(x => x.Key).ToString());
Func<int, int, bool> IsHit = (hx, hy) => dict.Any(dct => dct.Key / dict.Max(x => x.Key) == hx / dict.Max(x => x.Key) && dct.Value / dict.Max(x => x.Value) == hy / dict.Max(x => x.Value));
for (int i = 0; i < consoleHeight; i++)
{
Console.Write(i == 0 ? '┌' : '│');
for (int j = 0; j < consoleWidth; j++)
{
int actualheight = i * 2;
if (IsHit(j, actualheight) && IsHit(j, actualheight + 1))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write('█');
}
else if (IsHit(j, actualheight))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write('▀');
}
else if (IsHit(j, actualheight + 1))
{
Console.ForegroundColor = ConsoleColor.Black;
Console.BackgroundColor = ConsoleColor.Red;
Console.Write('▀');
}
}
Console.ResetColor();
Console.WriteLine();
}
Console.WriteLine('└' + new string('─', (consoleWidth / 2) - 1) + '┴' + new string('─', (consoleWidth / 2) - 1) + '┘');
Console.Write((dict.Min(x => x.Key) + "/" + dict.Min(x => x.Value)).PadRight(consoleWidth / 3));
Console.Write((dict.Max(x => x.Value) / 2).ToString().PadLeft(consoleWidth / 3 / 2).PadRight(consoleWidth / 3));
Console.WriteLine(dict.Max(x => x.Value).ToString().PadLeft(consoleWidth / 3));
}
回答1:
Code below should give you some idea.
First need to introduce a Point
, because working with Dictionary
and it's Key
and Value
properties instead of normal names like X
and Y
is a nightmare. Also, in dictionary you cannot store multiple points with the same X
coordinate, which makes little sense.
public struct Point {
public Point(int x, int y) {
this.X = x;
this.Y = y;
}
public int X { get; }
public int Y { get; }
}
Then a bit modified DrawChart
:
public static void DrawChart(List<Point> dict)
{
int consoleWidth = 78;
int consoleHeight = 20;
int actualConsoleHeight = consoleHeight * 2;
var minX = dict.Min(c => c.X);
var minY = dict.Min(c => c.Y);
var maxX = dict.Max(c => c.X);
var maxY = dict.Max(c => c.Y);
Console.WriteLine(maxX);
// normalize points to new coordinates
var normalized = dict.
Select(c => new Point(c.X - minX, c.Y - minY)).
Select(c => new Point((int)Math.Round((float) (c.X) / (maxX - minX) * (consoleWidth - 1)), (int)Math.Round((float) (c.Y) / (maxY - minY) * (actualConsoleHeight - 1)))).ToArray();
Func<int, int, bool> IsHit = (hx, hy) => {
return normalized.Any(c => c.X == hx && c.Y == hy);
};
for (int y = actualConsoleHeight - 1; y > 0; y -= 2)
{
Console.Write(y == actualConsoleHeight - 1 ? '┌' : '│');
for (int x = 0; x < consoleWidth; x++)
{
bool hitTop = IsHit(x, y);
bool hitBottom = IsHit(x, y - 1);
if (hitBottom && hitTop)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write('█');
}
else if (hitTop)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write('▀');
}
else if (hitBottom)
{
Console.ForegroundColor = ConsoleColor.Black;
Console.BackgroundColor = ConsoleColor.Red;
Console.Write('▀');
}
else
{
Console.ForegroundColor = ConsoleColor.Black;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write('▀');
}
}
Console.ResetColor();
Console.WriteLine();
}
Console.WriteLine('└' + new string('─', (consoleWidth / 2) - 1) + '┴' + new string('─', (consoleWidth / 2) - 1) + '┘');
Console.Write((dict.Min(x => x.X) + "/" + dict.Min(x => x.Y)).PadRight(consoleWidth / 3));
Console.Write((dict.Max(x => x.Y) / 2).ToString().PadLeft(consoleWidth / 3 / 2).PadRight(consoleWidth / 3));
Console.WriteLine(dict.Max(x => x.Y).ToString().PadLeft(consoleWidth / 3));
}
And usage:
static void Main(string[] args) {
var chartList = new List<Point> {
new Point(50, 31), // x = 50, y = 31
new Point(71, 87),
new Point(71, 89),
new Point(25, 66),
new Point(94, 15),
new Point(33, 94)
};
DrawChart(chartList);
Console.ReadKey();
}
Result:
回答2:
The real mistake is having a very long, one-line function. That makes it hard to read, and that causes problems even for an intelligent person like yourself.
IsHit() is a fairly complicated function, so spread it out...
You need to scale the coordinates, so they fit into the console window.
I have assumed window coordinates are (1,1) - (consoleWidth, consoleHeight)
private static bool IsConsoleHit(Dictionary<int, int> dict,
int consoleWidth,
int consoleHeight,
int hx,
int hy)
{
int minX = dict.Min(x => x.Key);
int maxX = dict.Max(x => x.Key);
int minY = dict.Min(x => x.Value);
int maxY = dict.Max(x => x.Value);
foreach (KeyValuePair<int, int> pair in dict)
{
// (x,y) starting at (0,0)
int x = pair.Key - minX;
int y = pair.Value - minY;
// convert to (0..1.0, 0..1.0)
double dx = x / Math.Max(maxX - minX, 1.0);
double dy = y / Math.Max(maxY - minY, 1.0);
// Scale to (1,1) upto (consoleWidth, consoleHeight)
int sx = (int)(dx * (consoleWidth - 1)) + 1;
int sy = (int)(dy * (consoleHeight - 1)) + 1;
if (hx == sx && hy == sy)
{
return true; // Its a hit
}
}
return false;
}
And then to make a lambda function out of it:
Func<int, int, bool> IsHit = ((hx, hy) => IsConsoleHit(dict, consoleWidth, consoleHeight, hx, hy);
So in future, I would never use one-line functions to calculate things. Use a normal method, giving you room to spread out and work.
回答3:
At least I don't have a answer that solves your problem, but a few hints, that maybe causes the trouble:
- Your coordinate system is wrong. The
IsHit()
method receives for the top left corner (where you start drawing) the coordinates 0/0, but it should be 0/19 and for the last element 77/19, but it should be 77/0. - For the calculation you divide integers, which leads to the fact that all fractions are lost and your query always returns true.
Maybe you should write a translation method, which will translate from the cursor position to the x/y value and then test against the translated position, instead of trying to do both in one step. I think by using that approach you will be able to tackle down the problem on yourself.
来源:https://stackoverflow.com/questions/38496889/console-chart-drawing