отображение оси от минимального до максимального значения - расчет шкалы и меток

Написание процедуры для отображения данных по горизонтальной оси (с использованием PHP gd2, но дело не в этом).

Ось начинается от $min до $max и отображает ромб в точке $result, такое изображение будет иметь ширину около 300 пикселей и высоту 30 пикселей, например:

example
(источник: testwolke.de < / а>)

В приведенном выше примере $min=0, $max=3, $result=0.6. Теперь мне нужно рассчитать масштаб и метки, которые имеют смысл, например, в приведенном выше примере. пунктирные линии на 0 .25 .50 .75 1 1.25 ... up to 3, с числовыми метками на 0 1 2 3.

Если $min=-200 и $max=600, пунктирные линии должны быть на -200 -150 -100 -50 0 50 100 ... up to 600, а числовые метки - на -200 -100 0 100 ... up to 600.

С $min=.02 и $max=5.80, пунктирными линиями в позиции .02 .5 1 1.5 2 2.5 ... 5.5 5.8 и числами в позиции .02 1 2 3 4 5 5.8.

Я попытался явно указать функции, где размещать пунктирные линии и числа по массивам, но эй, это компьютер должен работать, а не я, верно ?!

Итак, как рассчитать ???


person michi    schedule 17.02.2013    source источник
comment
ни один. Никто? хоть какой-то намек?   -  person michi    schedule 18.02.2013
comment
Есть ли определенное количество меток, которые вы хотите отображать на каждом изображении?   -  person Kyle    schedule 19.02.2013
comment
@Kyle: нет фиксированного количества меток, оно должно соответствовать значениям. Учитывая ширину в 300 пикселей, есть своего рода естественное ограничение.   -  person michi    schedule 19.02.2013
comment
Спасибо за ответ. Я опубликовал решение, которое даст вам указанное количество ярлыков. Я оставлю это здесь. Возможно, вы найдете хоть что-то из этого полезным.   -  person Kyle    schedule 19.02.2013
comment
связанное изображение не работает   -  person user568109    schedule 23.02.2013
comment
@ user568109 починил, спасибо!   -  person michi    schedule 24.02.2013


Ответы (4)


Алгоритм (примеры значений $min=-186 и $max=+153 в качестве пределов):

  1. Возьмите эти два ограничения $min, $max и отметьте их, если хотите.

  2. Рассчитайте разницу между $max и $min: $diff = $max - $min
    153 - (-186) = 339

  3. Вычислите 10-й логарифм разницы $base10 = log($diff,10) = 2,5302

  4. Округлите вниз: $power = round($base10) = 2.
    Это ваша десятая степень базовой единицы.

  5. Чтобы вычислить $step вычислите это:
    $base_unit = 10^$power = 100;
    $step = $base_unit / 2; (если хочешь 2 галочки на одну $base_unit).

  6. Рассчитайте, делится ли $min на $step, если нет, возьмите ближайшую (округленную) единицу
    (в случае $step = 50 это $loop_start = -150)

  7. for ($i=$loop_start; $i<=$max; $i++=$step){ // $i's are your ticks

  8. конец

Я тестировал его в Excel, и он дает неплохие результаты, возможно, вы захотите расширить его функциональность,

например (в пункте 5) путем вычисления $step сначала из $diff,
скажем $step = $diff / 4 и округления $step таким образом, чтобы $base_unit делилось на $step;

это позволит избежать таких ситуаций, когда у вас есть между (101; 201) четыре тика с $step=25 и у вас есть 39 шагов $step=25 от 0 до 999.

person Voitcus    schedule 26.02.2013
comment
это потрясающе, не могу дождаться, чтобы попробовать! - person michi; 26.02.2013
comment
michi, это было изобретено мной, не проверено слишком часто, я хотел дать вам ключ к разгадке. Наиболее важные моменты - это пункты 4 и 5. Я всегда округлял их в меньшую сторону, но, может быть, например, когда у вас 2,999, вам следует округлить их в большую сторону? Помните, что этот журнал (339,10) равен 2,5302, что будет округлено до 3 (что равно 1000). Это логарифмическая шкала, просто подумайте, как ее округлить, может быть, если больше 2,6989 (log 500) округлить в большую сторону, если меньше округлить в меньшую сторону? - person Voitcus; 27.02.2013
comment
эээ ... Я вернусь к вам, как только попытаюсь. Я должен признаться, что мои математические навыки ниже среднего, и ваши объяснения по поводу журнала заставляют меня пустить мозг ... поэтому я постараюсь сообщить :-) - person michi; 27.02.2013

Алгоритм ACM 463 предоставляет три простые функции для создания хороших масштабов осей с выходами xminp, xmaxp и dist для минимальное и максимальное значения на шкале и расстояние между делениями на шкале, учитывая запрос n интервалов, которые включают точки данных xmin и xmax:

  1. Scale1() дает линейную шкалу с примерно n интервалами, а dist является целочисленной степенью 10 умноженной на 1, 2 или 5.
  2. Scale2() дает линейную шкалу с ровно n интервалами (промежуток между xminp и xmaxp имеет тенденцию быть больше, чем промежуток, создаваемый Scale1()).
  3. Scale3() дает логарифмическую шкалу.

Оригинальный документ 1973 года находится в Интернете здесь, который дает больше объяснений, чем приведенный выше код.

Код написан на Фортране, но это всего лишь набор арифметических вычислений, поэтому его очень просто интерпретировать и преобразовывать в другие языки. Я сам не писал PHP, но он очень похож на C, поэтому вы можете начать с запуска кода через f2c, что должно дать вам что-то близкое к запускаемому на PHP.

Есть более сложные функции, которые дают более красивую шкалу (например, те, что в gnuplot), но Scale1(), скорее всего, сделает эту работу за вас с минимальным кодом.

(Этот ответ основан на моем ответе на предыдущий вопрос Калибровка оси графика в C ++ )

(РЕДАКТИРОВАТЬ - я нашел реализацию Scale1(), которую я сделал на Perl):

use strict;

sub scale1 ($$$) {
  # from TOMS 463
  # returns a suitable scale ($xMinp, $xMaxp, $dist), when called with 
  # the minimum and maximum x values, and an approximate number of intervals
  # to divide into. $dist is the size of each interval that results.
  # @vInt is an array of acceptable values for $dist.
  # @sqr is an array of geometric means of adjacent values of @vInt, which
  # is used as break points to determine which @vInt value to use.
  #
  my ($xMin, $xMax, $n) = @_;
  @vInt = {1, 2, 5, 10};
  @sqr = {1.414214, 3.162278, 7.071068 }
  if ($xMin > $xMax) {
    my ($tmp) = $xMin;
    $xMin = $xMax;
    $xMax = $tmp;
    }
  my ($del) = 0.0002;   # accounts for computer round-off
  my ($fn) = $n;
  # find approximate interval size $a
  my ($a) = ($xMax - $xMin) / $fn;
  my ($al) = log10($a);
  my ($nal) = int($al);
  if ($a < 1) {
    $nal = $nal - 1;
    }
  # $a is scaled into a variable named $b, between 1 and 10
  my ($b) = $a / 10^$nal;
  # the closest permissable value for $b is found)
  my ($i);
  for ($i = 0; $i < $_sqr; $i++) {
    if ($b < $sqr[$i]) last;
  }
  # the interval size is computed
  $dist = $vInt[$i] * 10^$nal;
  $fm1 = $xMin / $dist;
  $m1 = int($fm1);
  if ($fm1 < 0) $m1--;
  if (abs(($m1 + 1.0) - $fm1) < $del) $m1++;
  # the new minimum and maximum limits are found
  $xMinp = $dist * $m1;
  $fm2 = $xMax / $dist;
  $m2 = $fm2 + 1;
  if ($fm2 < -1) $m2--;
  if (abs ($fm2 + 1 - $m2) < $del) $m2--;
  $xMaxp = $dist * $m2;
  # adjust limits to account for round-off if necessary
  if ($xMinp > $xMin) $xMinp = $xMin;
  if ($xMaxp < $xMax) $xMaxp = $xMax;
  return ($xMinp, $xMaxp, $dist);
  }

sub scale1_Test {
  $par = (-3.1, 11.1, 5,
      5.2, 10.1, 5,
      -12000, -100, 9);
  print "xMin\txMax\tn\txMinp\txMaxp,dist\n";
  for ($i = 0; $i < $_par/3; $i++) {
    ($xMinp, $xMaxp, $dist) = scale1($par[3*$i+0], 
    $par[3*$i+1], $par[3*$i+2]);
    print "$par[3*$i+0]\t$par[3*$i+1]\t$par[3*$i+2]\t$xMinp\t$xMaxp,$dist\n";
    }
  }
person Simon    schedule 25.02.2013

Я знаю, что это не совсем то, что вы ищете, но, надеюсь, это поможет вам начать в правильном направлении.

$min = -200;
$max = 600;
$difference = $max - $min;
$labels = 10;
$picture_width = 300;
/* Get units per label */
$difference_between = $difference / ($labels - 1);
$width_between = $picture_width / $labels;
/* Make the label array */
$label_arr = array();
$label_arr[] = array('label' => $min, 'x_pos' => 0);
/* Loop through the number of labels */
for($i = 1, $l = $labels; $i < $l; $i++) {
    $label = $min + ($difference_between * $i);
    $label_arr[] = array('label' => $label, 'x_pos' => $width_between * $i);
}
person Kyle    schedule 19.02.2013
comment
хммм ... наградил этот ответ наградой (что тоже полезно) ... но хотел наградить алгоритм Войткуса ... ошибка новичка ... что я могу сделать? - person michi; 27.02.2013

Быстрый пример - это что-то в строках $increment = ($max-$min)/$scale, где вы можете настроить масштаб как переменную, по которой масштабируется приращение. Поскольку вы отклоняетесь от него, он должен меняться пропорционально изменению ваших максимальных и минимальных значений. После этого у вас будет такая функция, как:

$end = false;
while($end==false){
    $breakpoint = $last_value + $increment; // that's your current breakpoint
    if($breakpoint > $max){
        $end = true;
    } 
}

По крайней мере, такова концепция ... Дайте мне знать, если у вас с этим возникнут проблемы.

person Feras    schedule 26.02.2013