Разумное оптимизированное масштабирование диаграммы
Вопрос
Мне нужно сделать диаграмму с оптимизированным й максимальное значение оси.
Мой текущий метод создания диаграмм просто использует максимальное значение всех графиков, затем делит его на десять и использует его в качестве линий сетки.Я этого не писал.
Обновление Примечание: Эти графики были изменены.Как только я исправил код, мои динамические графики начали работать, что сделало этот вопрос бессмысленным (потому что в примерах больше не было ошибок).Я обновил их статическими изображениями, но в некоторых ответах указаны разные значения.Запомни.В феврале поступило от 12 003 до 14 003 входящих звонков.Информативно, но некрасиво.
Я хотел бы избегать графиков, которые выглядят так, будто обезьяна придумала йчисла по оси.
Использование API диаграмм Google немного помогает, но это все равно не совсем то, что мне нужно.Цифры чистые, но верхняя часть значения y всегда совпадает с максимальным значением на графике.Эта диаграмма масштабируется от 0 до 1357.Мне нужно рассчитать правильное значение 1400, проблематично.
я добавляю рбоббиЗдесь я даю определение «хорошему» числу, потому что оно так хорошо его объясняет.
- «Красивое» число — это число, состоящее из 3 или менее отличных от нуля цифр (например,1230000)
- «Красивое» число содержит столько же или несколько ненулевых цифр, чем нулевые цифры (например, 1230 — нехорошо, 1200 — хорошо).
- Самые красивые числа — это числа, кратные трем нулям (например,«1000», «1000000»)
- Вторые по красоте числа — это числа, кратные 3 нулям и 2 нулям (например.«1 500 000», «1 200»)
Решение
Я нашел способ получить желаемые результаты, используя модифицированную версию идеи Марка Рэнсома.
Во-первых, код Марка Рэнсома определяет оптимальное расстояние между тактами, если задано их количество.Иногда это число более чем в два раза превышает максимальное значение на диаграмме, в зависимости от того, сколько линий сетки вам нужно.
Я запускаю код Марка с 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);
}
Питон:
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)
Решение
Это из предыдущего аналогичного вопроса:
Алгоритм создания «красивых» интервалов сетки на графике
Я сделал это с помощью своего рода метода грубой силы.Во -первых, выясните максимальное количество отметок, которые вы можете вписать в пространство.Разделите общий диапазон значений на количество клещей;это минимумрасстояние между галочками.Теперь вычислите пол базы логарифма 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
Другие советы
Раньше я делал это методом грубой силы.Вот кусок кода C++, который работает хорошо...но для жестко запрограммированных нижнего и верхнего пределов (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];
}
Однако что-то в ваших словах меня смутило.Чтобы выбрать хорошие номера осей, поможет определение «красивого числа»:
- «Красивое» число — это число, состоящее из 3 или менее отличных от нуля цифр (например,1230000)
- «Красивое» число содержит столько же или несколько ненулевых цифр, чем нулевые цифры (например, 1230 — нехорошо, 1200 — хорошо).
- Самые красивые числа — это числа, кратные трем нулям (например,«1000», «1000000»)
- Вторые по красоте числа — это числа, кратные 3 нулям и 2 нулям (например.«1 500 000», «1 200»)
Не уверен, что приведенное выше определение является «правильным» или действительно полезным (но, имея определение в руках, разработка алгоритма становится более простой задачей).
Округлить можно до двух значащих цифр.Следующий псевдокод должен работать:
// 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).
Небольшая доработка и проверка...(работает для дробных единиц, а не только для целых чисел)
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);
}
Если вы хотите, чтобы вверху было 1400, как насчет настройки последних двух параметров на 1400 вместо 1357:
Вы можете использовать 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;