Я пишу небольшой код для отображения столбчатого (или линейного) графика в нашем программном обеспечении. Все идет хорошо. То, что меня озадачило, - это маркировка оси Y.
Звонящий может сказать мне, насколько точно они хотят пометить шкалу Y, но я, кажется, зациклился на том, что именно назвать им «привлекательным» способом. Я не могу описать "привлекательный", и, вероятно, вы тоже не можете, но мы узнаем это, когда видим это, верно?
Итак, если точки данных:
15, 234, 140, 65, 90
И пользователь просит 10 меток по оси Y, немного поигрался с бумагой и карандашом:
0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250
Итак, там 10 (не считая 0), последнее выходит за пределы самого высокого значения (234 <250), и это «хорошее» приращение по 25 каждое. Если бы они попросили 8 меток, было бы неплохо прибавить 30:
0, 30, 60, 90, 120, 150, 180, 210, 240
Девять было бы непросто. Может быть, просто использовал 8 или 10 и назвал бы это достаточно близко, было бы нормально. А что делать, если часть баллов отрицательная?
Я вижу, что Excel прекрасно справляется с этой проблемой.
Кто-нибудь знает алгоритм общего назначения (даже грубая сила в порядке) для решения этой проблемы? Мне не нужно делать это быстро, но все должно хорошо выглядеть.
Ответы:
Давным-давно я написал модуль графа, который хорошо это покрыл. Копаясь в серой массе, получаем следующее:
Возьмем ваш пример:
Итак, диапазон = 0,25,50, ..., 225,250
Вы можете получить хороший диапазон тиков, выполнив следующие действия:
В этом случае 21,9 делится на 10 ^ 2 и получается 0,219. Это <= 0,25, поэтому теперь у нас 0,25. Умноженное на 10 ^ 2, получаем 25.
Давайте посмотрим на тот же пример с 8 отметками:
Что дает результат, который вы запрашивали ;-).
------ Добавил KD ------
Вот код, который реализует этот алгоритм без использования таблиц поиска и т. Д.:
Вообще говоря, количество тактов включает нижнюю галочку, поэтому фактические сегменты оси Y на единицу меньше количества тактов.
источник
Вот пример PHP, который я использую. Эта функция возвращает массив красивых значений оси Y, которые включают переданные минимальные и максимальные значения Y. Конечно, эту процедуру также можно использовать для значений оси X.
Он позволяет вам «предложить», сколько тиков вы можете захотеть, но процедура вернет то, что выглядит хорошо. Я добавил несколько примеров данных и показал результаты для них.
#!/usr/bin/php -q <?php function makeYaxis($yMin, $yMax, $ticks = 10) { // This routine creates the Y axis values for a graph. // // Calculate Min amd Max graphical labels and graph // increments. The number of ticks defaults to // 10 which is the SUGGESTED value. Any tick value // entered is used as a suggested value which is // adjusted to be a 'pretty' value. // // Output will be an array of the Y axis values that // encompass the Y values. $result = array(); // If yMin and yMax are identical, then // adjust the yMin and yMax values to actually // make a graph. Also avoids division by zero errors. if($yMin == $yMax) { $yMin = $yMin - 10; // some small value $yMax = $yMax + 10; // some small value } // Determine Range $range = $yMax - $yMin; // Adjust ticks if needed if($ticks < 2) $ticks = 2; else if($ticks > 2) $ticks -= 2; // Get raw step value $tempStep = $range/$ticks; // Calculate pretty step value $mag = floor(log10($tempStep)); $magPow = pow(10,$mag); $magMsd = (int)($tempStep/$magPow + 0.5); $stepSize = $magMsd*$magPow; // build Y label array. // Lower and upper bounds calculations $lb = $stepSize * floor($yMin/$stepSize); $ub = $stepSize * ceil(($yMax/$stepSize)); // Build array $val = $lb; while(1) { $result[] = $val; $val += $stepSize; if($val > $ub) break; } return $result; } // Create some sample data for demonstration purposes $yMin = 60; $yMax = 330; $scale = makeYaxis($yMin, $yMax); print_r($scale); $scale = makeYaxis($yMin, $yMax,5); print_r($scale); $yMin = 60847326; $yMax = 73425330; $scale = makeYaxis($yMin, $yMax); print_r($scale); ?>
Результат вывода из выборки данных
источник
Попробуйте этот код. Я использовал его в нескольких сценариях построения графиков, и он хорошо работает. Это тоже довольно быстро.
public static class AxisUtil { public static float CalculateStepSize(float range, float targetSteps) { // calculate an initial guess at step size float tempStep = range/targetSteps; // get the magnitude of the step size float mag = (float)Math.Floor(Math.Log10(tempStep)); float magPow = (float)Math.Pow(10, mag); // calculate most significant digit of the new step size float magMsd = (int)(tempStep/magPow + 0.5); // promote the MSD to either 1, 2, or 5 if (magMsd > 5.0) magMsd = 10.0f; else if (magMsd > 2.0) magMsd = 5.0f; else if (magMsd > 1.0) magMsd = 2.0f; return magMsd*magPow; } }
источник
Похоже, вызывающий абонент не сообщает вам диапазоны, которые ему нужны.
Таким образом, вы можете изменять конечные точки до тех пор, пока они не будут точно делиться на количество ваших меток.
Определим «симпатичный». Я бы назвал хорошим, если бы ярлыки были отключены:
Найдите максимальные и минимальные значения вашего ряда данных. Назовем эти точки:
Теперь все, что вам нужно сделать, это найти 3 значения:
которые соответствуют уравнению:
Вероятно, существует множество решений, поэтому просто выберите одно. Бьюсь об заклад, в большинстве случаев вы можете установить
так что просто попробуйте другое целое число
пока смещение не станет "хорошим"
источник
Я все еще борюсь с этим :)
Исходный ответ Gamecat, кажется, работает большую часть времени, но попробуйте подключить, скажем, «3 тика» в качестве необходимого количества тиков (для тех же значений данных 15, 234, 140, 65, 90) .... кажется, дает диапазон тиков 73, который после деления на 10 ^ 2 дает 0,73, что соответствует 0,75, что дает «хороший» диапазон тиков 75.
Затем вычисляем верхнюю границу: 75 * раунд (1 + 234/75) = 300
и нижняя граница: 75 * раунд (15/75) = 0
Но очевидно, что если вы начнете с 0 и продвинетесь с шагом 75 до верхней границы 300, вы получите 0,75,150,225,300 .... что, несомненно, полезно, но это 4 такта (не включая 0), а не Требуется 3 галочки.
Просто расстраивает то, что он не работает в 100% случаев ... что, конечно, может быть связано с моей ошибкой!
источник
Ответ Toon Krijthe работает в большинстве случаев. Но иногда это приводит к избыточному количеству клещей. Он также не будет работать с отрицательными числами. Общий подход к проблеме приемлем, но есть способ лучше справиться с этим. Алгоритм, который вы хотите использовать, будет зависеть от того, что вы действительно хотите получить. Ниже я представляю вам свой код, который я использовал в своей библиотеке JS Ploting. Я тестировал его, и он всегда работает (надеюсь;)). Вот основные шаги:
Давайте начнем. Сначала основные расчеты
Я округляю минимальные и максимальные значения, чтобы быть на 100% уверенным, что мой график будет охватывать все данные. Также очень важно указать логарифм 10 диапазона, отрицательный он или нет, и вычесть 1 позже. В противном случае ваш алгоритм не будет работать для чисел меньше единицы.
Я использую «красивые галочки», чтобы избежать таких отметок, как 7, 13, 17 и т. Д. Метод, который я использую здесь, довольно прост. Также неплохо иметь zeroTick, когда это необходимо. Так сюжет выглядит намного профессиональнее. Вы найдете все методы в конце этого ответа.
Теперь вам нужно вычислить верхнюю и нижнюю границы. Это очень просто с нулевым тиком, но в другом случае потребуется немного больше усилий. Почему? Потому что мы хотим точно центрировать график в пределах верхней и нижней границы. Взгляни на мой код. Некоторые из переменных определены вне этой области, а некоторые из них являются свойствами объекта, в котором хранится весь представленный код.
И вот методы, о которых я упоминал ранее, которые вы можете написать самостоятельно, но вы также можете использовать мои
Есть только одна вещь, которой здесь нет. Это «красивые границы». Это нижние границы, которые представляют собой числа, похожие на числа в «красивых галочках». Например, лучше иметь нижнюю границу, начинающуюся с 5 с размером деления 5, чем иметь график, который начинается с 6 с таким же размером деления. Но это мой уволенный я оставляю это вам.
Надеюсь, поможет. Ура!
источник
Преобразовал этот ответ как Swift 4
extension Int { static func makeYaxis(yMin: Int, yMax: Int, ticks: Int = 10) -> [Int] { var yMin = yMin var yMax = yMax var ticks = ticks // This routine creates the Y axis values for a graph. // // Calculate Min amd Max graphical labels and graph // increments. The number of ticks defaults to // 10 which is the SUGGESTED value. Any tick value // entered is used as a suggested value which is // adjusted to be a 'pretty' value. // // Output will be an array of the Y axis values that // encompass the Y values. var result = [Int]() // If yMin and yMax are identical, then // adjust the yMin and yMax values to actually // make a graph. Also avoids division by zero errors. if yMin == yMax { yMin -= ticks // some small value yMax += ticks // some small value } // Determine Range let range = yMax - yMin // Adjust ticks if needed if ticks < 2 { ticks = 2 } else if ticks > 2 { ticks -= 2 } // Get raw step value let tempStep: CGFloat = CGFloat(range) / CGFloat(ticks) // Calculate pretty step value let mag = floor(log10(tempStep)) let magPow = pow(10,mag) let magMsd = Int(tempStep / magPow + 0.5) let stepSize = magMsd * Int(magPow) // build Y label array. // Lower and upper bounds calculations let lb = stepSize * Int(yMin/stepSize) let ub = stepSize * Int(ceil(CGFloat(yMax)/CGFloat(stepSize))) // Build array var val = lb while true { result.append(val) val += stepSize if val > ub { break } } return result } }
источник
это работает как шарм, если вы хотите 10 шагов + ноль
источник
Для всех, кому это нужно в ES5 Javascript, немного поборолись, но вот оно:
На основе отличного ответа Toon Krijtje.
источник
Это решение основано на найденном мной примере Java .
const niceScale = ( minPoint, maxPoint, maxTicks) => { const niceNum = ( localRange, round) => { var exponent,fraction,niceFraction; exponent = Math.floor(Math.log10(localRange)); fraction = localRange / Math.pow(10, exponent); if (round) { if (fraction < 1.5) niceFraction = 1; else if (fraction < 3) niceFraction = 2; else if (fraction < 7) niceFraction = 5; else niceFraction = 10; } else { if (fraction <= 1) niceFraction = 1; else if (fraction <= 2) niceFraction = 2; else if (fraction <= 5) niceFraction = 5; else niceFraction = 10; } return niceFraction * Math.pow(10, exponent); } const result = []; const range = niceNum(maxPoint - minPoint, false); const stepSize = niceNum(range / (maxTicks - 1), true); const lBound = Math.floor(minPoint / stepSize) * stepSize; const uBound = Math.ceil(maxPoint / stepSize) * stepSize; for(let i=lBound;i<=uBound;i+=stepSize) result.push(i); return result; }; console.log(niceScale(15,234,6)); // > [0, 100, 200, 300]
источник
Спасибо за вопрос и ответ, очень полезно. Gamecat, мне интересно, как вы определяете, до какого диапазона тиков следует округлять.
Чтобы сделать это алгоритмически, нужно было бы добавить логику к приведенному выше алгоритму, чтобы этот масштаб был удобен для больших чисел? Например, с 10 тиками, если диапазон равен 3346, тогда диапазон тиков будет равен 334,6, а округление до ближайших 10 даст 340, тогда как 350, вероятно, лучше.
Что вы думаете?
источник
На основе алгоритма @ Gamecat я создал следующий вспомогательный класс
источник
Вышеупомянутые алгоритмы не учитывают случай, когда диапазон между минимальным и максимальным значением слишком мал. А что, если эти значения намного больше нуля? Затем у нас есть возможность начать ось Y со значением выше нуля. Кроме того, чтобы наша линия не находилась полностью в верхней или нижней части графика, мы должны дать ей немного «воздуха для дыхания».
Чтобы охватить эти случаи, я написал (на PHP) приведенный выше код:
источник