การเลือกสเกลเชิงเส้นที่น่าสนใจสำหรับแกน Y ของกราฟ


84

ฉันกำลังเขียนโค้ดเล็กน้อยเพื่อแสดงกราฟแท่ง (หรือเส้น) ในซอฟต์แวร์ของเรา ทุกอย่างเป็นไปด้วยดี สิ่งที่ทำให้ฉันนิ่งงันคือการติดป้ายแกน 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 แก้ไขปัญหานี้ได้ดี

ไม่มีใครรู้อัลกอริทึมวัตถุประสงค์ทั่วไป (แม้แต่พลังเดรัจฉานบางตัวก็ใช้ได้) สำหรับการแก้ปัญหานี้ ไม่ต้องรีบทำ แต่ก็น่าดู


1
มีข้อมูลบางอย่างเกี่ยวกับวิธีที่ Excel เลือกค่าสูงสุดและต่ำสุดสำหรับแกน Y ที่นี่: support.microsoft.com/kb/214075
Christopher Orr

การใช้งานที่ดี: stackoverflow.com/a/16363437/829571
assylias

คำตอบ:


103

เมื่อนานมาแล้วฉันได้เขียนโมดูลกราฟที่ครอบคลุมสิ่งนี้อย่างดี การขุดในมวลสีเทาจะได้รับสิ่งต่อไปนี้:

  • กำหนดขอบเขตล่างและบนของข้อมูล (ระวังกรณีพิเศษที่ขอบเขตล่าง = ขอบเขตบน!
  • แบ่งช่วงออกเป็นจำนวนเห็บที่ต้องการ
  • ปัดเศษขึ้นเป็นจำนวนที่เหมาะสม
  • ปรับขอบเขตล่างและบนให้เหมาะสม

ลองใช้ตัวอย่างของคุณ:

15, 234, 140, 65, 90 with 10 ticks
  1. ขอบเขตล่าง = 15
  2. ขอบเขตบน = 234
  3. ช่วง = 234-15 = 219
  4. ช่วงขีด = 21.9 นี่ควรเป็น 25.0
  5. ขอบเขตล่างใหม่ = 25 * รอบ (15/25) = 0
  6. ขอบบนใหม่ = 25 * รอบ (1 + 235/25) = 250

ดังนั้นช่วง = 0,25,50, ... , 225,250

คุณสามารถรับช่วงเห็บที่ดีได้ด้วยขั้นตอนต่อไปนี้:

  1. หารด้วย 10 ^ x เพื่อให้ผลลัพธ์อยู่ระหว่าง 0.1 ถึง 1.0 (รวม 0.1 ไม่รวม 1)
  2. แปลตาม:
    • 0.1 -> 0.1
    • <= 0.2 -> 0.2
    • <= 0.25 -> 0.25
    • <= 0.3 -> 0.3
    • <= 0.4 -> 0.4
    • <= 0.5 -> 0.5
    • <= 0.6 -> 0.6
    • <= 0.7 -> 0.7
    • <= 0.75 -> 0.75
    • <= 0.8 -> 0.8
    • <= 0.9 -> 0.9
    • <= 1.0 -> 1.0
  3. คูณด้วย 10 ^ x

ในกรณีนี้ 21.9 หารด้วย 10 ^ 2 จะได้ 0.219 นี่คือ <= 0.25 ดังนั้นตอนนี้เรามี 0.25 คูณด้วย 10 ^ 2 ได้ 25

มาดูตัวอย่างเดียวกันกับ 8 เห็บ:

15, 234, 140, 65, 90 with 8 ticks
  1. ขอบเขตล่าง = 15
  2. ขอบเขตบน = 234
  3. ช่วง = 234-15 = 219
  4. ช่วงขีด = 27.375
    1. หารด้วย 10 ^ 2 สำหรับ 0.27375 แปลเป็น 0.3 ซึ่งให้ (คูณด้วย 10 ^ 2) 30
  5. ขอบล่างใหม่ = 30 * รอบ (15/30) = 0
  6. ขอบบนใหม่ = 30 * รอบ (1 + 235/30) = 240

ซึ่งให้ผลลัพธ์ที่คุณร้องขอ ;-)

------ เพิ่มโดย KD ------

นี่คือรหัสที่บรรลุอัลกอริทึมนี้โดยไม่ต้องใช้ตารางค้นหา ฯลฯ ... :

double range = ...;
int tickCount = ...;
double unroundedTickSize = range/(tickCount-1);
double x = Math.ceil(Math.log10(unroundedTickSize)-1);
double pow10x = Math.pow(10, x);
double roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
return roundedTickRange;

โดยทั่วไปจำนวนเห็บจะรวมขีดล่างด้วยดังนั้นกลุ่มแกน y ที่แท้จริงจึงน้อยกว่าจำนวนเห็บ


1
แค่นี้ก็ถูกแล้ว ขั้นตอนที่ 3 ฉันต้องลด X ลง 1 เพื่อให้ได้ช่วง 219 ถึง. 1-> 1 ฉันต้องหารด้วย 10 ^ 3 (1000) ไม่ใช่ 10 ^ 2 (100) มิฉะนั้นจุด
คลินตันเพียร์ซ

2
คุณอ้างอิงการหารด้วย 10 ^ x และคูณด้วย 10 ^ x ควรสังเกตว่า x สามารถพบได้ด้วยวิธีนี้: 'double x = Math.Ceiling (Math.Log10 (tickRange));'
ไบรอัน

1
เป็นประโยชน์มาก แม้ว่าจะไม่เข้าใจ - 'ขอบเขตล่างใหม่ = 30 * รอบ (15/30) = 0' (ฉันคิดว่ามันจะมา 30) และวิธีที่คุณได้ 235 ใน 'ขอบเขตบนใหม่ = 30 * รอบ (1 + 235/30) = 240 '235 ไม่มีการกล่าวถึงที่ไหนเลยมันควรจะเป็น 234
มนุษย์กลายพันธุ์

4
นี่คือคำตอบที่ยอดเยี่ยม ชื่นชมมาก.
Joel Anair

4
@JoelAnair ขอบคุณที่ทำให้วันที่เศร้าสดใสขึ้นมาหน่อย
Toon Krijthe

22

นี่คือตัวอย่าง 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);
?>

ผลลัพธ์ผลลัพธ์จากข้อมูลตัวอย่าง

# ./test1.php
Array
(
    [0] => 60
    [1] => 90
    [2] => 120
    [3] => 150
    [4] => 180
    [5] => 210
    [6] => 240
    [7] => 270
    [8] => 300
    [9] => 330
)

Array
(
    [0] => 0
    [1] => 90
    [2] => 180
    [3] => 270
    [4] => 360
)

Array
(
    [0] => 60000000
    [1] => 62000000
    [2] => 64000000
    [3] => 66000000
    [4] => 68000000
    [5] => 70000000
    [6] => 72000000
    [7] => 74000000
)

เจ้านายของฉันจะมีความสุขกับสิ่งนี้ - โหวตจากฉันด้วยขอบคุณมาก !!
Stephen Hazel

ตอบโจทย์มาก! ฉันแปลงเป็นSwift 4 stackoverflow.com/a/55151115/2670547
Petr Syrov

@Scott Guthrie: สิ่งนี้ดีมากเว้นแต่ว่าอินพุตไม่ใช่จำนวนเต็มและเป็นตัวเลขขนาดเล็กเช่นถ้า yMin = 0.03 และ yMax = 0.11
Greg

9

ลองใช้รหัสนี้ ฉันเคยใช้มันในสถานการณ์การสร้างแผนภูมิไม่กี่สถานการณ์และทำงานได้ดี ค่อนข้างเร็วด้วย

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;
    }
}

6

ดูเหมือนว่าผู้โทรไม่ได้บอกช่วงที่ต้องการ

คุณจึงมีอิสระที่จะเปลี่ยนจุดสิ้นสุดจนกว่าคุณจะหารด้วยจำนวนป้ายกำกับของคุณได้อย่างลงตัว

ให้คำจำกัดความว่า "ดี" ฉันจะเรียกว่าดีถ้าป้ายกำกับปิดโดย:

1. 2^n, for some integer n. eg. ..., .25, .5, 1, 2, 4, 8, 16, ...
2. 10^n, for some integer n. eg. ..., .01, .1, 1, 10, 100
3. n/5 == 0, for some positive integer n, eg, 5, 10, 15, 20, 25, ...
4. n/2 == 0, for some positive integer n, eg, 2, 4, 6, 8, 10, 12, 14, ...

ค้นหาค่าสูงสุดและต่ำสุดของชุดข้อมูลของคุณ เรียกคะแนนเหล่านี้ว่า

min_point and max_point.

ตอนนี้สิ่งที่คุณต้องทำคือค้นหา 3 ค่า:

- start_label, where start_label < min_point and start_label is an integer
- end_label, where end_label > max_point and end_label is an integer
- label_offset, where label_offset is "nice"

ที่เหมาะสมกับสมการ:

(end_label - start_label)/label_offset == label_count

อาจมีวิธีแก้ปัญหามากมายเพียงเลือกวิธีใดวิธีหนึ่ง ส่วนใหญ่ฉันเดิมพันคุณสามารถตั้งค่าได้

start_label to 0

ลองใช้จำนวนเต็มอื่น

end_label

จนกว่าค่าชดเชยจะ "ดี"


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% ของเวลา .... ซึ่งอาจเป็นความผิดพลาดของฉันได้แน่นอน!


เดิมทีคิดว่าปัญหาอาจเกี่ยวข้องกับวิธีการหาค่า x ที่แนะนำของไบรอัน แต่แน่นอนว่านี่เป็นสิ่งที่ถูกต้องสมบูรณ์
StillPondering

3

คำตอบของToon Krijtheทำงานเกือบตลอดเวลา แต่บางครั้งก็จะผลิตเห็บมากเกินไป มันจะใช้ไม่ได้กับจำนวนลบเช่นกัน แนวทางที่ครอบคลุมในการแก้ปัญหานั้นใช้ได้ แต่มีวิธีจัดการที่ดีกว่านี้ อัลกอริทึมที่คุณต้องการใช้จะขึ้นอยู่กับสิ่งที่คุณต้องการได้รับจริงๆ ด้านล่างนี้ฉันกำลังนำเสนอโค้ดของฉันที่ฉันใช้ในไลบรารี JS Ploting ฉันทดสอบแล้วและใช้งานได้เสมอ (หวังว่า;)) ขั้นตอนสำคัญมีดังนี้

  • รับ xMin และ xMax เอกซ์เทรมัสทั่วโลก (รวมพล็อตทั้งหมดที่คุณต้องการพิมพ์ในอัลกอริทึม)
  • คำนวณช่วงระหว่าง xMin และ xMax
  • คำนวณลำดับขนาดของช่วงของคุณ
  • คำนวณขนาดเห็บโดยหารช่วงด้วยจำนวนเห็บลบหนึ่ง
  • อันนี้เป็นทางเลือก หากคุณต้องการให้มีการพิมพ์เครื่องหมายขีดเป็นศูนย์ให้คุณใช้ขนาดเห็บเพื่อคำนวณจำนวนเห็บบวกและลบ จำนวนเห็บทั้งหมดจะเป็นผลรวม + 1 (เครื่องหมายศูนย์)
  • ไม่จำเป็นต้องใช้อันนี้หากคุณพิมพ์เครื่องหมายขีดทั้งหมดเป็นศูนย์ คำนวณขอบเขตล่างและบน แต่อย่าลืมวางพล็อตไว้ตรงกลาง

เริ่มกันเลย. ก่อนอื่นการคำนวณพื้นฐาน

    var range = Math.abs(xMax - xMin); //both can be negative
    var rangeOrder = Math.floor(Math.log10(range)) - 1; 
    var power10 = Math.pow(10, rangeOrder);
    var maxRound = (xMax > 0) ? Math.ceil(xMax / power10) : Math.floor(xMax / power10);
    var minRound = (xMin < 0) ? Math.floor(xMin / power10) : Math.ceil(xMin / power10);

ฉันปัดเศษค่าต่ำสุดและสูงสุดเพื่อให้แน่ใจ 100% ว่าพล็อตของฉันจะครอบคลุมข้อมูลทั้งหมด นอกจากนี้ยังมีความสำคัญมากในการบันทึกพื้น 10 ของ range wheter หรือไม่ก็เป็นค่าลบและลบ 1 ในภายหลัง มิฉะนั้นอัลกอริทึมของคุณจะใช้ไม่ได้กับตัวเลขที่น้อยกว่าหนึ่ง

    var fullRange = Math.abs(maxRound - minRound);
    var tickSize = Math.ceil(fullRange / (this.XTickCount - 1));

    //You can set nice looking ticks if you want
    //You can find exemplary method below 
    tickSize = this.NiceLookingTick(tickSize);

    //Here you can write a method to determine if you need zero tick
    //You can find exemplary method below
    var isZeroNeeded = this.HasZeroTick(maxRound, minRound, tickSize);

ฉันใช้ "เห็บที่ดูดี" เพื่อหลีกเลี่ยงเห็บเช่น 7, 13, 17 เป็นต้นวิธีที่ฉันใช้ที่นี่ค่อนข้างง่าย นอกจากนี้ยังดีที่มี zeroTick เมื่อจำเป็น พล็อตดูเป็นมืออาชีพมากขึ้นด้วยวิธีนี้ คุณจะพบวิธีการทั้งหมดในตอนท้ายของคำตอบนี้

ตอนนี้คุณต้องคำนวณขอบเขตบนและล่าง นี่เป็นเรื่องง่ายมากโดยมีเครื่องหมายเป็นศูนย์ แต่ต้องใช้ความพยายามมากกว่าในกรณีอื่น ทำไม? เพราะเราต้องการจัดกึ่งกลางของพล็อตภายในขอบเขตบนและล่างให้ดี ดูรหัสของฉัน ตัวแปรบางตัวถูกกำหนดไว้นอกขอบเขตนี้และตัวแปรบางตัวเป็นคุณสมบัติของอ็อบเจกต์ที่เก็บโค้ดที่นำเสนอทั้งหมดไว้

    if (isZeroNeeded) {

        var positiveTicksCount = 0;
        var negativeTickCount = 0;

        if (maxRound != 0) {

            positiveTicksCount = Math.ceil(maxRound / tickSize);
            XUpperBound = tickSize * positiveTicksCount * power10;
        }

        if (minRound != 0) {
            negativeTickCount = Math.floor(minRound / tickSize);
            XLowerBound = tickSize * negativeTickCount * power10;
        }

        XTickRange = tickSize * power10;
        this.XTickCount = positiveTicksCount - negativeTickCount + 1;
    }
    else {
        var delta = (tickSize * (this.XTickCount - 1) - fullRange) / 2.0;

        if (delta % 1 == 0) {
            XUpperBound = maxRound + delta;
            XLowerBound = minRound - delta;
        }
        else {
            XUpperBound =  maxRound + Math.ceil(delta);
            XLowerBound =  minRound - Math.floor(delta);
        }

        XTickRange = tickSize * power10;
        XUpperBound = XUpperBound * power10;
        XLowerBound = XLowerBound * power10;
    }

และนี่คือวิธีการที่ฉันพูดถึงก่อนหน้านี้ซึ่งคุณสามารถเขียนได้ด้วยตัวเอง แต่คุณสามารถใช้ของฉันได้ด้วย

this.NiceLookingTick = function (tickSize) {

    var NiceArray = [1, 2, 2.5, 3, 4, 5, 10];

    var tickOrder = Math.floor(Math.log10(tickSize));
    var power10 = Math.pow(10, tickOrder);
    tickSize = tickSize / power10;

    var niceTick;
    var minDistance = 10;
    var index = 0;

    for (var i = 0; i < NiceArray.length; i++) {
        var dist = Math.abs(NiceArray[i] - tickSize);
        if (dist < minDistance) {
            minDistance = dist;
            index = i;
        }
    }

    return NiceArray[index] * power10;
}

this.HasZeroTick = function (maxRound, minRound, tickSize) {

    if (maxRound * minRound < 0)
    {
        return true;
    }
    else if (Math.abs(maxRound) < tickSize || Math.round(minRound) < tickSize) {

        return true;
    }
    else {

        return false;
    }
}

มีเพียงสิ่งเดียวที่ไม่รวมอยู่ที่นี่ นี่คือ "ขอบเขตที่ดูดี" นี่คือขอบเขตล่างที่เป็นตัวเลขคล้ายกับตัวเลขใน "เห็บที่ดูดี" ตัวอย่างเช่นควรให้ขอบเขตล่างเริ่มต้นที่ 5 ด้วยขีดขนาด 5 ดีกว่าการมีพล็อตที่เริ่มต้นที่ 6 ด้วยขนาดขีดเท่ากัน แต่นี่เป็นความผิดของฉันฉันทิ้งไว้ให้คุณ

หวังว่าจะช่วยได้ ไชโย!


2

แปลงคำตอบนี้เป็น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
    }

}

สิ่งนี้ดีมากเว้นแต่อินพุตจะไม่ใช่จำนวนเต็มและเป็นตัวเลขขนาดเล็กเช่นถ้า yMin = 0.03 และ yMax = 0.11
Greg

1

สิ่งนี้ใช้งานได้อย่างมีเสน่ห์หากคุณต้องการ 10 ก้าว + ศูนย์

//get proper scale for y
$maximoyi_temp= max($institucion); //get max value from data array
 for ($i=10; $i< $maximoyi_temp; $i=($i*10)) {   
    if (($divisor = ($maximoyi_temp / $i)) < 2) break; //get which divisor will give a number between 1-2    
 } 
 $factor_d = $maximoyi_temp / $i;
 $factor_d = ceil($factor_d); //round up number to 2
 $maximoyi = $factor_d * $i; //get new max value for y
 if ( ($maximoyi/ $maximoyi_temp) > 2) $maximoyi = $maximoyi /2; //check if max value is too big, then split by 2

1

สำหรับใครที่ต้องการสิ่งนี้ใน ES5 Javascript ลองต่อสู้สักหน่อย แต่นี่คือ:

var min=52;
var max=173;
var actualHeight=500; // 500 pixels high graph

var tickCount =Math.round(actualHeight/100); 
// we want lines about every 100 pixels.

if(tickCount <3) tickCount =3; 
var range=Math.abs(max-min);
var unroundedTickSize = range/(tickCount-1);
var x = Math.ceil(Math.log10(unroundedTickSize)-1);
var pow10x = Math.pow(10, x);
var roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
var min_rounded=roundedTickRange * Math.floor(min/roundedTickRange);
var max_rounded= roundedTickRange * Math.ceil(max/roundedTickRange);
var nr=tickCount;
var str="";
for(var x=min_rounded;x<=max_rounded;x+=roundedTickRange)
{
    str+=x+", ";
}
console.log("nice Y axis "+str);    

จากคำตอบที่ยอดเยี่ยมของ Toon Krijtje


1

โซลูชันนี้อ้างอิงจากตัวอย่าง 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]


0

ขอบคุณสำหรับคำถามและคำตอบมีประโยชน์มาก Gamecat ฉันสงสัยว่าคุณกำลังพิจารณาว่าช่วงของเห็บควรจะปัดเศษเป็นอย่างไร

ช่วงขีด = 21.9 นี่ควรเป็น 25.0

ในการทำตามอัลกอริทึมเราจะต้องเพิ่มลอจิกให้กับอัลกอริทึมด้านบนเพื่อสร้างมาตราส่วนนี้ให้เหมาะกับตัวเลขที่มากขึ้น? ตัวอย่างเช่นมี 10 เห็บถ้าช่วงคือ 3346 ดังนั้นช่วงเห็บจะประเมินเป็น 334.6 และการปัดเศษเป็น 10 ที่ใกล้ที่สุดจะให้ 340 เมื่อ 350 น่าจะดีกว่า

คุณคิดอย่างไร?


ในตัวอย่างของ @ Gamecat 334.6 => 0.3346 ซึ่งควรไปที่ 0.4 ดังนั้นช่วงเห็บจะเป็น 400 ซึ่งเป็นตัวเลขที่ดีทีเดียว
Bryan

0

จากอัลกอริทึมของ @ Gamecat ฉันสร้างคลาสตัวช่วยต่อไปนี้

public struct Interval
{
    public readonly double Min, Max, TickRange;

    public static Interval Find(double min, double max, int tickCount, double padding = 0.05)
    {
        double range = max - min;
        max += range*padding;
        min -= range*padding;

        var attempts = new List<Interval>();
        for (int i = tickCount; i > tickCount / 2; --i)
            attempts.Add(new Interval(min, max, i));

        return attempts.MinBy(a => a.Max - a.Min);
    }

    private Interval(double min, double max, int tickCount)
    {
        var candidates = (min <= 0 && max >= 0 && tickCount <= 8) ? new[] {2, 2.5, 3, 4, 5, 7.5, 10} : new[] {2, 2.5, 5, 10};

        double unroundedTickSize = (max - min) / (tickCount - 1);
        double x = Math.Ceiling(Math.Log10(unroundedTickSize) - 1);
        double pow10X = Math.Pow(10, x);
        TickRange = RoundUp(unroundedTickSize/pow10X, candidates) * pow10X;
        Min = TickRange * Math.Floor(min / TickRange);
        Max = TickRange * Math.Ceiling(max / TickRange);
    }

    // 1 < scaled <= 10
    private static double RoundUp(double scaled, IEnumerable<double> candidates)
    {
        return candidates.First(candidate => scaled <= candidate);
    }
}

0

อัลกอริทึมข้างต้นไม่ได้คำนึงถึงกรณีที่ช่วงระหว่างค่าต่ำสุดและค่าสูงสุดน้อยเกินไป แล้วถ้าค่าเหล่านี้สูงกว่าศูนย์เยอะล่ะ? จากนั้นเรามีความเป็นไปได้ที่จะเริ่มแกน y ด้วยค่าที่สูงกว่าศูนย์ นอกจากนี้เพื่อหลีกเลี่ยงไม่ให้เส้นของเราอยู่ด้านบนหรือด้านล่างของกราฟทั้งหมดเราต้องให้ "อากาศหายใจ" กับมัน

เพื่อให้ครอบคลุมกรณีเหล่านั้นฉันเขียน (บน PHP) โค้ดด้านบน:

function calculateStartingPoint($min, $ticks, $times, $scale) {

    $starting_point = $min - floor((($ticks - $times) * $scale)/2);

    if ($starting_point < 0) {
        $starting_point = 0;
    } else {
        $starting_point = floor($starting_point / $scale) * $scale;
        $starting_point = ceil($starting_point / $scale) * $scale;
        $starting_point = round($starting_point / $scale) * $scale;
    }
    return $starting_point;
}

function calculateYaxis($min, $max, $ticks = 7)
{
    print "Min = " . $min . "\n";
    print "Max = " . $max . "\n";

    $range = $max - $min;
    $step = floor($range/$ticks);
    print "First step is " . $step . "\n";
    $available_steps = array(5, 10, 20, 25, 30, 40, 50, 100, 150, 200, 300, 400, 500);
    $distance = 1000;
    $scale = 0;

    foreach ($available_steps as $i) {
        if (($i - $step < $distance) && ($i - $step > 0)) {
            $distance = $i - $step;
            $scale = $i;
        }
    }

    print "Final scale step is " . $scale . "\n";

    $times = floor($range/$scale);
    print "range/scale = " . $times . "\n";

    print "floor(times/2) = " . floor($times/2) . "\n";

    $starting_point = calculateStartingPoint($min, $ticks, $times, $scale);

    if ($starting_point + ($ticks * $scale) < $max) {
        $ticks += 1;
    }

    print "starting_point = " . $starting_point . "\n";

    // result calculation
    $result = [];
    for ($x = 0; $x <= $ticks; $x++) {
        $result[] = $starting_point + ($x * $scale);
    }
    return $result;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.