Разумное оптимизированное масштабирование диаграммы
Мне нужно составить диаграмму с максимальным значением максимальной оси y.
В текущем методе создания диаграмм просто используется максимальное значение всех графиков, затем делит его на десять и использует это как линии сетки. Я не писал это.
Обновить Примечание: Эти графики были изменены. Как только я исправил код, мои динамические графики начали работать, делая этот вопрос бессмысленным (потому что в примерах больше не было ошибок). Я обновил их со статическими изображениями, но некоторые ответы подтверждают разные значения. Запомни.
alt text http://i42.tinypic.com/nwzr5s.jpg
В феврале между входящими звонками было 12003 и 14003. Информативный, но уродливый.
Я бы хотел избежать диаграмм, которые выглядят так, как обезьяна придумала номера оси y.
Использование API диаграмм Google помогает немного, но это все еще не совсем то, что я хочу.
Числа чистые, но верхняя часть значения y всегда совпадает с максимальным значением на графике. Эта диаграмма масштабируется от 0 до 1357. Мне нужно вычислить правильное значение 1400, проблематично.
Я забрасываю rbobby из-за "хорошего" номера здесь, потому что он так хорошо объясняет.
- "Хорошее" число - это число, которое содержит 3 или менее ненулевых цифр (например, 1230000).
- "Хорошее" число имеет одинаковые или несколько ненулевых цифр, чем нулевые цифры (например, 1230 не очень хорошо, 1200 - приятный).
- Самые красивые цифры - это те, у которых кратные 3 нули (например, "1,000", "1,000,000" ).
- Вторые самые приятные числа: onces с множеством из 3 нулей плюс 2 нуля (например, "1500 000", "1200" ).
Решение
alt text http://i43.tinypic.com/21jc0no.png
Я нашел способ получить результаты, которые я хочу использовать модифицированную версию идеи Марка Рэнсом.
Fist, код Mark Ransom определяет оптимальное расстояние между тиками при задании количества тиков. Иногда это число больше, чем в два раза больше, чем самое высокое значение на графике, в зависимости от того, сколько строк сетки вы хотите.
Что я делаю, так это то, что я запускаю Mark с 5, 6, 7, 8, 9 и 10 сетками (тиками), чтобы определить, какая из них самая низкая. При значении 23 высота диаграммы составляет 25, а линия сетки - 5, 10, 15, 20 и 25. При значении 26 высота диаграммы составляет 30, а линии сетки - 5, 10, 15, 20, 25 и 30. Он имеет такое же расстояние между линиями сетки, но их больше.
Итак, вот шаги, которые нужно сделать, чтобы скопировать то, что делает Excel, чтобы все графики выглядели.
- Временно поднимите максимальное значение графика примерно на 5% (так что между верхней точкой графика и верхней частью области графика всегда есть пробел. Мы хотим, чтобы 99.9 округлялось до 120).
- Найдите оптимальное размещение сетки
для сетки 5, 6, 7, 8, 9 и 10
линии.
- Выберите самый низкий из этих чисел. Помните количество линий сетки, которые потребовались для получения этого значения.
- Теперь у вас есть оптимальная высота диаграммы. Строки/бар никогда не будут опираться на верхнюю часть диаграммы, и у вас будет оптимальное количество тиков.
PHP:
function roundUp($maxValue){
$optiMax = $maxValue * 2;
for ($i = 5; $i <= 10; $i++){
$tmpMaxValue = bestTick($maxValue,$i);
if (($optiMax > $tmpMaxValue) and ($tmpMaxValue > ($maxValue + $maxValue * 0.05))){
$optiMax = $tmpMaxValue;
$optiTicks = $i;
}
}
return $optiMax;
}
function bestTick($maxValue, $mostTicks){
$minimum = $maxValue / $mostTicks;
$magnitude = pow(10,floor(log($minimum) / log(10)));
$residual = $minimum / $magnitude;
if ($residual > 5){
$tick = 10 * $magnitude;
} elseif ($residual > 2) {
$tick = 5 * $magnitude;
} elseif ($residual > 1){
$tick = 2 * $magnitude;
} else {
$tick = $magnitude;
}
return ($tick * $mostTicks);
}
Python:
import math
def BestTick(largest, mostticks):
minimum = largest / mostticks
magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
residual = minimum / magnitude
if residual > 5:
tick = 10 * magnitude
elif residual > 2:
tick = 5 * magnitude
elif residual > 1:
tick = 2 * magnitude
else:
tick = magnitude
return tick
value = int(input(""))
optMax = value * 2
for i in range(5,11):
maxValue = BestTick(value,i) * i
print maxValue
if (optMax > maxValue) and (maxValue > value + (value*.05)):
optMax = maxValue
optTicks = i
print "\nTest Value: " + str(value + (value * .05)) + "\n\nChart Height: " + str(optMax) + " Ticks: " + str(optTicks)
Ответы
Ответ 1
Это из предыдущего аналогичного вопроса:
Алгоритм для "приятного" интервалы сетки на графике
Я сделал это с видом грубых метод силы. Во-первых, выясните максимальное количество отметок, которые вы можете вписывается в пространство. Разделить общее количество диапазон значений по числу тики; это минимуминтервал тика. Теперь вычислим пол логарифмической базы 10 до получить величину галочки и разделите на это значение. Вы должны закончить с чем-то в диапазоне от 1 до 10. Просто выберите круглое число, большее или равное значению, и умножить его на логарифм рассчитанный ранее. Это ваш окончательный интервал тикета.
Пример в Python:
import math
def BestTick(largest, mostticks):
minimum = largest / mostticks
magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
residual = minimum / magnitude
if residual > 5:
tick = 10 * magnitude
elif residual > 2:
tick = 5 * magnitude
elif residual > 1:
tick = 2 * magnitude
else:
tick = magnitude
return tick
Ответ 2
В прошлом я делал это в грубой силовой форме. Вот кусок кода на С++, который хорошо работает... но для жестко закодированных нижних и верхних пределов (0 и 5000):
int PickYUnits()
{
int MinSize[8] = {20, 20, 20, 20, 20, 20, 20, 20};
int ItemsPerUnit[8] = {5, 10, 20, 25, 50, 100, 250, 500};
int ItemLimits[8] = {20, 50, 100, 250, 500, 1000, 2500, 5000};
int MaxNumUnits = 8;
double PixelsPerY;
int PixelsPerAxis;
int Units;
//
// Figure out the max from the dataset
// - Min is always 0 for a bar chart
//
m_MinY = 0;
m_MaxY = -9999999;
m_TotalY = 0;
for (int j = 0; j < m_DataPoints.GetSize(); j++) {
if (m_DataPoints[j].m_y > m_MaxY) {
m_MaxY = m_DataPoints[j].m_y;
}
m_TotalY += m_DataPoints[j].m_y;
}
//
// Give some space at the top
//
m_MaxY = m_MaxY + 1;
//
// Figure out the size of the range
//
double yRange = (m_MaxY - m_MinY);
//
// Pick the initial size
//
Units = MaxNumUnits;
for (int k = 0; k < MaxNumUnits; k++)
{
if (yRange < ItemLimits[k])
{
Units = k;
break;
}
}
//
// Adjust it upwards based on the space available
//
PixelsPerY = m_rcGraph.Height() / yRange;
PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);
while (PixelsPerAxis < MinSize[Units]){
Units += 1;
PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);
if (Units == 5)
break;
}
return ItemsPerUnit[Units];
}
Однако что-то в том, что вы сказали, изменило меня. Чтобы выбрать красивые номера оси, определение "nice number" поможет:
- "Хорошее" число - это число, которое содержит 3 или менее ненулевых цифр (например, 1230000).
- "Хорошее" число имеет одинаковые или несколько ненулевых цифр, чем нулевые цифры (например, 1230 не очень хорошо, 1200 - приятный).
- Самые красивые цифры - это те, у которых кратные 3 нули (например, "1,000", "1,000,000" ).
- Вторые самые приятные числа: onces с множеством из 3 нулей плюс 2 нуля (например, "1500 000", "1200" ).
Не уверен, что указанное выше определение является "правильным" или действительно полезным (но с определением в руке оно становится более простой задачей для разработки алгоритма).
Ответ 3
Вы можете округлить до двух значащих цифр. Следующий псевдокод должен работать:
// maxValue is the largest value in your chart
magnitude = floor(log10(maxValue))
base = 10^(magnitude - 1)
chartHeight = ceiling(maxValue / base) * base
Например, если maxValue
равно 1357, тогда значение равно 3, а основание равно 100. Разделение на 100, округление и умножение на 100 имеет результат округления до следующего кратного 100, то есть округление до две значимые цифры. В этом случае результат, если 1400 (1357 ⇒ 13,57 ⇒ 14 ⇒ 1400).
Ответ 4
Небольшое уточнение и тестирование... (работает для фракций единиц, а не только целых чисел)
public void testNumbers() {
double test = 0.20000;
double multiple = 1;
int scale = 0;
String[] prefix = new String[]{"", "m", "u", "n"};
while (Math.log10(test) < 0) {
multiple = multiple * 1000;
test = test * 1000;
scale++;
}
double tick;
double minimum = test / 10;
double magnitude = 100000000;
while (minimum <= magnitude){
magnitude = magnitude / 10;
}
double residual = test / (magnitude * 10);
if (residual > 5) {
tick = 10 * magnitude;
} else if (residual > 2) {
tick = 5 * magnitude;
} else if (residual > 1) {
tick = 2 * magnitude;
} else {
tick = magnitude;
}
double curAmt = 0;
int ticks = (int) Math.ceil(test / tick);
for (int ix = 0; ix < ticks; ix++) {
curAmt += tick;
BigDecimal bigDecimal = new BigDecimal(curAmt);
bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println(bigDecimal.stripTrailingZeros().toPlainString() + prefix[scale] + "s");
}
System.out.println("Value = " + test + prefix[scale] + "s");
System.out.println("Tick = " + tick + prefix[scale] + "s");
System.out.println("Ticks = " + ticks);
System.out.println("Scale = " + multiple + " : " + scale);
}
Ответ 5
Если вы хотите 1400 в верхней части, как насчет настройки последних двух параметров на 1400 вместо 1357:
![alt text]()
Ответ 6
Вы можете использовать div и mod. Например.
Скажем, вы хотите, чтобы ваш график округлялся с шагом 20 (просто чтобы сделать его более произвольным числом, чем ваше типичное значение "10" ).
Итак, я бы предположил, что 1, 11, 18 будут округлены до 20. Но 21, 33, 38 будут раундом до 40.
Чтобы найти правильное значение, сделайте следующее:
Where divisor = your rounding increment.
divisor = 20
multiple = maxValue / divisor; // Do an integer divide here.
if (maxValue modulus divisor > 0)
multiple++;
graphMax = multiple * maxValue;
Итак, теперь допустим действительные числа плагина:
divisor = 20;
multiple = 33 / 20; (integer divide)
so multiple = 1
if (33 modulus 20 > 0) (it is.. it equals 13)
multiple++;
so multiple = 2;
graphMax = multiple (2) * maxValue (20);
graphMax = 40;