I'm trying to draw a signal response on a chart and I need a logarithmic scale for X and Y.
I defined two functions, one for X axis
private void Configure_Axis_X(bool Logaritmic, double Maximum, double Minimum, double Interval)
{
CH_EQ_Chart.ChartAreas[0].AxisX.IsLogarithmic = Logaritmic;
CH_EQ_Chart.ChartAreas[0].AxisX.Minimum = Minimum;
CH_EQ_Chart.ChartAreas[0].AxisX.Maximum = Maximum;
CH_EQ_Chart.ChartAreas[0].AxisX.Interval = Interval;
CH_EQ_Chart.ChartAreas[0].AxisX.MajorGrid.LineColor = Color.Black;
CH_EQ_Chart.ChartAreas[0].AxisX.MajorGrid.LineDashStyle = Graph.ChartDashStyle.Dash;
}
and one for Y axis
private void Configure_Axis_Y(bool Logaritmic, double Maximum, double Minimum, double Interval)
{
CH_EQ_Chart.ChartAreas[0].AxisY.IsLogarithmic = Logaritmic;
CH_EQ_Chart.ChartAreas[0].AxisY.Minimum = Minimum;
CH_EQ_Chart.ChartAreas[0].AxisY.Maximum = Maximum;
CH_EQ_Chart.ChartAreas[0].AxisY.Interval = Interval;
CH_EQ_Chart.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.Black;
CH_EQ_Chart.ChartAreas[0].AxisY.MajorGrid.LineDashStyle = Graph.ChartDashStyle.Dash;
}
the response to draw is expressed in Decibel and I'd like to have logarithmic scale also for Y.
When I have the array with values, I get minimum and maxim value and I try to use the function above with
double Abs_Max = Math.Max(y.Max(), z.Max());
double Abs_Min = Math.Min(y.Min(), z.Min());
Configure_Axis_Y(true, Abs_Max + Abs_Max/10, Abs_Min + Abs_Min/10, 20);
but when I select islogaritmic = true a red cross appears instead the graph.
If I set islogaritmic = false the picture appears right.
The red cross is the chart's way of handling an exception during drawing. The most likely culprit when dealing with log scale is that one or more data points have zero or negative values. Check in the debugger what Abs_Min is when the method is called, as it's likely you're somehow getting zero or negative values in there.
Related
I'm trying to create a random float generator (range of 0.0-1.0), where I can supply a single target value, and a strength value that increases or decreases the chance that this target will be hit. For example, if my target is 0.7, and I have a high strength value, I would expect the function to return mostly values around 0.7.
Put another way, I want a function that, when run a lot of times, would produce a distribution graph something like this:
Histogram
Something like a bell curve, yes, but with a strict range limit (instead of the -inf/+inf range limit of a normal distribution). Clamping a normal distribution is not ideal, I want the distribution to naturally end at the range limits.
The approach I've been attempting is to come up with a formula to transform a value from uniform distribution to the mythical distribution I'm envisioning. Something like an inverse sine:
Inverse Sine
with the ability to widen out that middle point, via the strength value:
Widened Midpoint
and also the ability to move that midpoint up and down, via the target value:
Target changed to 0.7 (courtesy of MS Paint because I couldn't figure this part out mathematically)
The range of this theoretical "strength value" is up for debate. I could imagine either a limited value, say between 0 and 1, where 0 means it's uniform distribution and 1 means it's a 100% chance of hitting the target; or, I could imagine a value that approaches a 100% chance the higher it gets, without ever reaching it. Something along either line would work.
I'm working in C# but this can be language-agnostic. Any help pointing me in the right direction is appreciated. Also happy to clarify further.
I'm not a mathematician but I took a look and I feel like I got something that might work for you.
All i did was take the normal distribution formula:
and use 0.7 as mu to shift the distribution towards 0.7. I added a leading coefficient of 0.623 to shift the values to be between 0 and 1 and migrated it from formula to C#, this can be found below.
Usage:
DistributedRandom random = new DistributedRandom();
// roll for the chance to hit
double roll = random.NextDouble();
// add a strength modifier to lower or strengthen the roll based on level or something
double actualRoll = 0.7d * roll;
Definition
public class DistributedRandom : Random
{
public double Mean { get; set; } = 0.7d;
private const double limit = 0.623d;
private const double alpha = 0.25d;
private readonly double sqrtOf2Pi;
private readonly double leadingCoefficient;
public DistributedRandom()
{
sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
leadingCoefficient *= limit;
}
public override double NextDouble()
{
double x = base.NextDouble();
double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);
double result = leadingCoefficient * Math.Pow(Math.E,exponent);
return result;
}
}
Edit:
In case you're not looking for output similar to the distribution histogram that you provided and instead want something more similar to the sigmoid function you drew I have created an alternate version.
Thanks to Ruzihm for pointing this out.
I went ahead and used the CDF for the normal distribution: where erf is defined as the error function: . I added a coefficient of 1.77 to scale the output to keep it within 0d - 1d.
It should produce numbers similar to this:
Here you can find the alternate class:
public class DistributedRandom : Random
{
public double Mean { get; set; } = 0.7d;
private const double xOffset = 1d;
private const double yOffset = 0.88d;
private const double alpha = 0.25d;
private readonly double sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
private readonly double leadingCoefficient;
private const double cdfLimit = 1.77d;
private readonly double sqrt2 = Math.Sqrt(2);
private readonly double sqrtPi = Math.Sqrt(Math.PI);
private readonly double errorFunctionCoefficient;
private readonly double cdfDivisor;
public DistributedRandom()
{
leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
errorFunctionCoefficient = 2d / sqrtPi;
cdfDivisor = alpha * sqrt2;
}
public override double NextDouble()
{
double x = base.NextDouble();
return CDF(x) - yOffset;
}
private double DistributionFunction(double x)
{
double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);
double result = leadingCoefficient * Math.Pow(Math.E, exponent);
return result;
}
private double ErrorFunction(double x)
{
return errorFunctionCoefficient * Math.Pow(Math.E,-Math.Pow(x,2));
}
private double CDF(double x)
{
x = DistributionFunction(x + xOffset)/cdfDivisor;
double result = 0.5d * (1 + ErrorFunction(x));
return cdfLimit * result;
}
}
I came up with a workable solution. This isn't quite as elegant as I was aiming for because it requires 2 random numbers per result, but it definitely fulfills the requirement. Basically it takes one random number, uses another random number that's exponentially curved towards 1, and lerps towards the target.
I wrote it out in python because it was easier for me to visualize the histogram of it:
import math
import random
# Linearly interpolate between a and b by t.
def lerp(a, b, t):
return ((1.0 - t) * a) + (t * b)
# What we want the median value to be.
target = 0.7
# How often we will hit that median value. (0 = uniform distribution, higher = greater chance of hitting median)
strength = 1.0
values = []
for i in range(0, 1000):
# Start with a base float between 0 and 1.
base = random.random()
# Get another float between 0 and 1, that trends towards 1 with a higher strength value.
adjust = random.random()
adjust = 1.0 - math.pow(1.0 - adjust, strength)
# Lerp the base float towards the target by the adjust amount.
value = lerp(base, target, adjust)
values.append(value)
# Graph histogram
import matplotlib.pyplot as plt
import scipy.special as sps
count, bins, ignored = plt.hist(values, 50, density=True)
plt.show()
Target = 0.7, Strength = 1
Target = 0.2, Strength = 1
Target = 0.7, Strength = 3
Target = 0.7, Strength = 0
(This is meant to be uniform distribution - it might look kinda jagged, but I tested and that's just python's random number generator.)
I'm plotting a few different LineSeries in Oxyplot (in C#, using Windows Forms), which all have wildly different ranges. To make every series still visible, I'm scaling all their values to the range from 0 to 1. Of course the problem here is, that the actual values of the series can't be displayed anymore, so I wondered if it's possible to change the left-click event that displays the X and Y value of a DataPoint to fix this. If possible, I'd like it so that when the user clicks on a datapoint, the displayed Y-value would be scaled back to the original, while the graph remains scaled down.
For example, I have a Na+ value ranging from 130 to 150, which I then scale to 0 and 1. But when the user clicks on a Datapoint, I want it to display Y = 140 and not 0.5. Since every LineSeries has a different scaling factor, this would also mean that I'd have to edit the label for each series seperately.
So yeah, is something like that possible in the current version? Thanks in advance!
Edit: I figured it out, see my answer below!
I'm not sure if I'm supposed to answer my own question, but I ended up figuring it out. So anyway, it turns out I can't access any of the properties needed, so I just made my own class for the Datapoints and included a property for the scaled value. It turns out that the Tracker is capable of displaying other properties of the Datapoints, but the standard Datapoints only has X and Y. Then I modified the TrackerFormatString to show the scaled value instead of the actual one.
public class MeasurePoint : IDataPointProvider
{
public MeasurePoint(double x, double y, double scale)
{
X = x; Y = y; Scale = scale;
}
public double X { get; set; }
public double Y { get; set; }
public double Scale { get; set; }
public DataPoint GetDataPoint()
{
return new DataPoint(X, Y);
}
}
That is the class I created and this is how I ended up handling the points.
var points = new MeasurePoint[Y.Count - 1];
for (int i = 0; i < Y.Count; i++)
{
points[i] = new MeasurePoint(DateTimeAxis.ToDouble(X[i]), Y[i], Y[i]*scale);
}
Series.ItemsSource = points;
Series.TrackerFormatString = "{0}\n{2}\n{Scale}";
scale here is the factor that I divide the values with before plotting and the TrackerFormatString is {0} the Series name and {2} is the X value.
Works pretty great!
You can't directly change the displayed data, however you can listen to the mousedown event within the oxyplot like this:
var model= new PlotModel();
model.MouseDown += Model_MouseDown;
private void Model_MouseDown(object sender, OxyMouseDownEventArgs e)
{
var controller = sender as PlotController;
var position = e.HitTestResult;
}
With the values of position you can calculate back to the actual value and display it somewhere else.
I use the C# Chart in WinForms to plot a variety of variables in real time using the "line" chart type. That works well for analog values, but it's less than ideal for on/off flags.
I'd like to plot multiple flags as horizontal bars that are filled when the value is '1" and clear when the value is '0'.
Before I start coding a solution from scratch, do you have any suggestion on how I could take advantage of any features of the "chart" object to implement this more effectively?
EDIT: I am playing with the Area type, and it seems to be promising.
EDIT 2: That didn't work, because the area in the Area type always starts at the bottom of the chart, hiding the other rows. I am now trying the Range Column type
There are several ways to tackle this.: StackedBars, AreaChart, Annotations but I think by far the simplest is using a LineChartType.
The first issue is: How to create the gaps? The simplest way is to draw them as lines but with Color.Transparent. So instead of using the flag value as our y-value we use it to set the color..
So we could use a function like this:
void AddFlagLine(Chart chart, int series, int flag, int x)
{
Series s = chart.Series[series];
int px = s.Points.AddXY(x, series);
s.Points[px].Color = s.Color;
if (px > 0) s.Points[px - 1].Color = flag == 1 ? s.Color : Color.Transparent;
}
It takes the index of your Series and uses the flag to determine the color; note that the color of a line segment is controlled by the color of the end point.
So if you want to have the line going out from the new point to have its flag color, you need to set it when adding the next one..
This is simple enough and for lines as thick as 1-10 it works fine. But if you want larger widths things get a bit ugly..:
The rounded caps start to get bigger and bigger until they actually touch, flling the gaps more or less.
Unfortunately there seems to be no way to controls the caps-style of the lines. There are many CustomAttributes including DashStyles but not this one. So we have to resort to owner-drawing. This is rather simple for line charts. Here is an example:
The xxxPaint event looks like this:
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Graphics g = e.ChartGraphics.Graphics;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
for (int si = 0; si < chart.Series.Count; si++ )
{
Series s = chart.Series[si];
for (int pi = 1; pi < s.Points.Count - 1; pi++)
{
DataPoint dp = s.Points[pi];
int y = (int) ay.ValueToPixelPosition(dp.YValues[0]+1); ///*1*
int x0 = (int)ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int)ax.ValueToPixelPosition(s.Points[pi-1].XValue); ///*2*
int x2 = (int)ax.ValueToPixelPosition(dp.XValue);
x1 = Math.Max(x1, x0);
x2 = Math.Max(x2, x0);
using (Pen pen = new Pen(dp.Color, 40) ///*3*
{ StartCap = System.Drawing.Drawing2D.LineCap.Flat,
EndCap = System.Drawing.Drawing2D.LineCap.Flat })
{
g.DrawLine(pen, x1, y, x2, y);
}
}
}
A few notes:
1 : I have decided to move the the series up by one; this is up to you just as using or turning off the y-axis labels or replacing them by custom labels..
2 : Here we use the previous point's x-position!
3 : Note that instead of hard coding a width of 40 pixels you really should decide on a calculated width. This is an example that almost fills up the area:
int width = (int)( ( ay.ValueToPixelPosition(ay.Minimum) -
ay.ValueToPixelPosition(ay.Maximum)) / (chart7.Series.Count + 2));
You can twist is to fill more or less by adding less or more than 2.
I have turned all BorderWidths to 0 so only the drawn lines show.
I got it:
It turned out to actually be pretty easy; I used the Range Column type.
A) Set-up (done once):
plotChart.Series[chanNo].ChartType = SeriesChartType.RangeColumn;
plotChart.Series[chanNo].CustomProperties = "PointWidth=" + noOfFlags;
PointWidth is required to set the relative width of each rectangle so that it fills the entire width of one data point (if too small, there are gaps in the horizontal bar; if too large, there is overlap). noOfFlags is the number of flags shown (in the example shown above, noOfFlags = 4). (By the way the MSDN documentation is wrong: PointWidth is not limited to 2.)
B) Plotting (done for each new data point):
baseLine--;
int barHeight = flagHigh ? 1 : 0;
plotChart.Series[chanNo].Points.AddXY(pointX, baseLine, baseLine + barHeight);
flagHigh is a bool that is equal to the flag being monitored.
baseLine is decremented for each trace. In the example above, baseLine starts at 4, and is decremented down to 0.
Note that for each data point, RangeColumn requires 2 "Y" values: one for the bottom of the rectangle, one for the top; in the code, I set the bottom Y to the bottom of the row that I use for that particular flag, and the top to 1 above the bottom, to give me a height of 1.
I have a treeView element where every node represent a double list.
I'm using a DataVisualization.Charting control to display the values in the list.
For some of the lists I get an exception after RecalculateAxesScale (System.OverflowException: Value was either too large or too small for a Decimal).
I ignore this error and therefore the chart displays a big red cross.
When I now click on another node I want to display the chart of this double list (which is valid), but my chart is not redrawn. It always displays the red X.
My code:
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
//Refresh chart:
chart1.Series.Clear();
chart1.ResetAutoValues();
chart1.ResetText();
//plot new doublelist
var series = new Series
{
Name = "name",
Color = color,
ChartType = SeriesChartType.Line,
ChartArea = "chartName"
};
this.chart1.Series.Add(series);
series.Points.DataBindY(doubleList);
var chartArea = chart1.ChartAreas["chartName"];
chartArea.RecalculateAxesScale();
chartArea.AxisX.Minimum = 1;
chartArea.AxisX.Maximum = doubleList.Count;
chartArea.CursorX.AutoScroll = true;
chartArea.CursorY.AutoScroll = true;
// Allow user to select area for zooming
chartArea.CursorX.IsUserEnabled = true;
chartArea.CursorX.IsUserSelectionEnabled = true;
// Set automatic zooming`<br>
chartArea.AxisX.ScaleView.Zoomable = true;
chartArea.AxisY.ScaleView.Zoomable = true;
chartArea.AxisX.ScrollBar.IsPositionedInside = true;
//reset zoom
chartArea.AxisX.ScaleView.ZoomReset();
chart1.Invalidate();
}
[EDIT]
Type of dblList:
List<double> doubleList= (from s in myData select s.value).ToList();
Full stack of Exceptions:
{System.OverflowException: Value was either too large or too small for a Decimal.
at System.Decimal.FCallMultiply(Decimal& d1, Decimal& d2)
at System.Decimal.op_Multiply(Decimal d1, Decimal d2)
at System.Windows.Forms.DataVisualization.Charting.Axis.RoundedValues(Double inter, Boolean shouldStartFromZero, Boolean autoMax, Boolean autoMin, Double& min, Double& max)
at System.Windows.Forms.DataVisualization.Charting.Axis.EstimateNumberAxis(Double& minimumValue, Double& maximumValue, Boolean shouldStartFromZero, Int32 preferredNumberOfIntervals, Boolean autoMaximum, Boolean autoMinimum)
at System.Windows.Forms.DataVisualization.Charting.Axis.EstimateAxis(Double& minimumValue, Double& maximumValue, Boolean autoMaximum, Boolean autoMinimum)
at System.Windows.Forms.DataVisualization.Charting.Axis.EstimateAxis()
at System.Windows.Forms.DataVisualization.Charting.ChartArea.SetDefaultAxesValues()
at System.Windows.Forms.DataVisualization.Charting.ChartArea.SetData(Boolean initializeAxes, Boolean checkIndexedAligned)
at System.Windows.Forms.DataVisualization.Charting.ChartArea.RecalculateAxesScale()
[EDIT 2]
Example List:
List<double> dblList = new List<double>();
dblList.Add(0.0);
dblList.Add(-7.4876421623346545E-36);
dblList.Add(1.0);
dblList.Add(-26697097281536.0);
dblList.Add(-6.8163553952838136E+28); //problem!!!!!
The last value produces the problem (red cross without exception). So it seems that the min and max values for converting the list are not appropriate. Any ideas on that?
double min = (double)Decimal.MinValue; //min = -7.9228162514264338E+28
double max = (double)Decimal.MaxValue; //max = 7.9228162514264338E+28
The error in RecalculateAxesScale (System.OverflowException: Value was either too large or too small for a Decimal) was thrown, because of a Bug in the Library. It uses decimal values inside the implementation of chart functions (especially in axes recalculation/zoom), which causes the problem.
Just checking for every chart point if its value is lower or greater than
double min = (double)Decimal.MinValue;
double max = (double)Decimal.MaxValue;
and replace it by this values didn't solve the problem.
I chose -/+7.92E+27 as bounds instead of the ones above and everything is working fine now.
So the solution proffered thus far is almost, but not quite, correct. If it was open source I could find an remedy the problem directly, but alas I must only surmise as to the actual implementation. The chart library appears to convert to Decimal, and throws an exception when the value range is exceeded while converting from double. The obvious solution of checking for all out-of-range values and setting to Decimal.MinValue and Decimal.MaxValue fails with the same exception. In fact, the actual max and min values allowed by the charting engine was found by myself, through some experimentation, to be approximately an order of magnitude less.
Enough discussion, here is my working source code for a TVQ chart with sometimes bound-exceeding double values. Max and min chart values are pre-calculated for efficiency. Replace all calls to AddXY with SafeChartDouble. Change the AxisX datatype as necessary for your application.
private static readonly double SCALE_FACTOR = 10;
private static readonly double MIN_CHART_VALUE
= Convert.ToDouble(Decimal.MinValue) / SCALE_FACTOR;
private static readonly double MAX_CHART_VALUE
= Convert.ToDouble(Decimal.MaxValue) / SCALE_FACTOR;
private void SafeChartDouble(Series cs, DateTime ts, double dv)
{
// microsoft chart component breaks on very large/small values
double chartv;
if (dv < MIN_CHART_VALUE)
{
chartv = MIN_CHART_VALUE;
}
else if (dv > MAX_CHART_VALUE)
{
chartv = MAX_CHART_VALUE;
}
else
{
chartv = dv;
}
cs.Points.AddXY(ts, chartv);
}
I'm graphing some statistics which can be percentages, currency values or plain numbers.
I need to set the maximum value of the graph control's axis to a nice, round number just a bit above the maximum value in the data set. (The graph control's default value is not what I want).
Two things to note:
The value I set for the axis maximum should be minimum 5% above the dataset's maximum value (the less above this the better).
I have 4 horizontal lines above the 0 Y-axis; so ideally the Y-axis maximum should divide nicely by 4.
Sample data might be:
200%, 100%, 100%, 100%, 75%, 50%, 9%
In this case, 220% would be acceptable as the maximum value.
$3500161, $1825223, $1671232, $110112
In this case, $3680000 might be ok. Or $3700000 I suppose.
Can anyone suggest a nice formula for doing this? I might need to adjust settings, like the 5% margin might be changed to 10%, or I might need to change the 4 horizontal lines to 5.
Here is the code I use to create graph axes.
/// <summary>
/// Axis scales a min/max value appropriately for the purpose of graphs
/// <remarks>Code taken and modified from http://peltiertech.com/WordPress/calculate-nice-axis-scales-in-excel-vba/</remarks>
/// </summary>
public struct Axis
{
public readonly float min_value;
public readonly float max_value;
public readonly float major_step;
public readonly float minor_step;
public readonly int major_count;
public readonly int minor_count;
/// <summary>
/// Initialize Axis from range of values.
/// </summary>
/// <param name="x_min">Low end of range to be included</param>
/// <param name="x_max">High end of range to be included</param>
public Axis(float x_min, float x_max)
{
//Check if the max and min are the same
if(x_min==x_max)
{
x_max*=1.01f;
x_min/=1.01f;
}
//Check if dMax is bigger than dMin - swap them if not
if(x_max<x_min)
{
float temp = x_min;
x_min = x_max;
x_max = temp;
}
//Make dMax a little bigger and dMin a little smaller (by 1% of their difference)
float delta=(x_max-x_min)/2;
float x_mid=(x_max+x_min)/2;
x_max=x_mid+1.01f*delta;
x_min=x_mid-1.01f*delta;
//What if they are both 0?
if(x_max==0&&x_min==0)
{
x_max=1;
}
//This bit rounds the maximum and minimum values to reasonable values
//to chart. If not done, the axis numbers will look very silly
//Find the range of values covered
double pwr=Math.Log(x_max-x_min)/Math.Log(10);
double scl=Math.Pow(10, pwr-Math.Floor(pwr));
//Find the scaling factor
if(scl>0&&scl<=2.5)
{
major_step=0.2f;
minor_step=0.05f;
}
else if(scl>2.5&&scl<5)
{
major_step=0.5f;
minor_step=0.1f;
}
else if(scl>5&&scl<7.5)
{
major_step=1f;
minor_step=0.2f;
}
else
{
major_step=2f;
minor_step=0.5f;
}
this.major_step=(float)(Math.Pow(10, Math.Floor(pwr))*major_step);
this.minor_step=(float)(Math.Pow(10, Math.Floor(pwr))*minor_step);
this.major_count=(int)Math.Ceiling((x_max-x_min)/major_step);
this.minor_count=(int)Math.Ceiling((x_max-x_min)/minor_step);
int i_1=(int)Math.Floor(x_min/major_step);
int i_2=(int)Math.Ceiling(x_max/major_step);
this.min_value=i_1*major_step;
this.max_value=i_2*major_step;
}
public float[] MajorRange
{
get
{
float[] res=new float[major_count+1];
for(int i=0; i<res.Length; i++)
{
res[i]=min_value+major_step*i;
}
return res;
}
}
public float[] MinorRange
{
get
{
float[] res=new float[minor_count+1];
for(int i=0; i<res.Length; i++)
{
res[i]=min_value+minor_step*i;
}
return res;
}
}
}
You can the nice max_value and min_value as calculated from the initialized for Axis given the mathematical min. max. values in x_min and x_max.
Example:
new Axis(0,3500161) calculates max_value = 4000000.0
new Axis(0,1825223) calculates max_value = 2000000.0
new Axis(0,1671232) calculates max_value = 1800000.0
new Axis(0, 110112) calculates max_value = 120000.0
For your 1st query use:
DataView data = new DataView(dt);
string strTarget = dt.Compute("MAX(target)", string.Empty).ToString();// target is your column name.
int tTarget = int.Parse(strTarget.Equals("") ? "0" : strTarget); // Just in case if your string is empty.
myChart.ChartAreas[0].AxisY.Maximum = myChart.ChartAreas[0].AxisY2.Maximum = Math.Ceiling(tTarget * 1.1); // This will give a 10% plus to max value.
For the 2nd point, i guess you can figure this out with minor/major axis interlaced and offset properties.
First, you'll need to decide on a range for (top of graph)/(max data point). You have this bounded on the lower end as 1.05; reasonable upper bounds might be 1.1 or 1.15. The wider the range, the more empty space may appear at the top of the graph, but the "nicer" the numbers may be. Alternatively, you can pick a "niceness" criterion first and then pick the smallest sufficiently nice number where the above ratio is at least 1.05.
You can also improve the "niceness" of the intervals by loosening that lower bound, for instance lowering it to 1.02 or even 1.0.
EDIT: In response to comment.
What you'll have to do to find a good max size is take your max value plus margin, divide it by the number of intervals, round it upward to the nearest "nice" value, and multiply by the number of intervals. A reasonable definition of "nice" might be "multiple of 10^(floor(log_10(max value)) - 2)" A looser definition of niceness will give you (on average) less extra margin at the top.