Search code examples
javascriptmath

Math help for a simple Chart


I have 5 horizontal lines and each has a distance of 30 px. The first horizontal line display the max value, the last the min value. Now I have an array with 13 items with different values. Basically I want show LINES (Chart) on the horizontal lines due the value of the array, for every item from the array.

I try to use what @Mina recommended me but it don't works correct.

      var prices = [];
  prices.push(28990, 30240, 30890, 32162, 31000, 31820, 31770, 30080, 28340, 28620, 28640, 27930, 28850);

  var minPrice = Math.floor(Math.min(...prices)/1000)*1000;
  var maxPrice = Math.ceil(Math.max(...prices)/1000)*1000;

  function addText(x, y, t) {
    var text = document.createElementNS('http://www.w3.org/2000/svg','text');
    
    text.setAttribute('x', x);     
    text.setAttribute('y', y); 
    text.setAttribute('fill', 'black');

    var textNode = document.createTextNode(t);
    text.appendChild(textNode);
    
    document.getElementById('main').appendChild(text);
  }
 
  function addLine(id, x1, y1, x2, y2, sc,sw) {
    const line = document.createElementNS('http://www.w3.org/2000/svg','line');
    
    line.setAttribute('id', id);
    line.setAttribute('x1', x1);
    line.setAttribute('y1', y1);
    line.setAttribute('x2', x2);
    line.setAttribute('y2', y2);
    line.setAttribute('stroke', sc);
    line.setAttribute('stroke-width', sw);

    document.getElementById("main").appendChild(line);
  }
  
  function addHorizontalLinesPrice() {
    var m = maxPrice;
    addText(20, 55, m);
    addText(20, 85, m = m-1500);
    addText(20, 115, m = m-1500);
    addText(20, 145, m = m-1500);
    addText(20, 175, m = m-1500);
  }
  
  addHorizontalLinesPrice();
  
  function addHorizontalLines() {
    var y = 50.5;
    
    for (var i = 0; i < 5; i++) {
      addLine("horizontalLine"+(i+1), 90, y, window.innerWidth-20, y, "black", 0.5);
      y = y + 30;
    }
  }
  
  addHorizontalLines();

  var totalHeight = 100;

  function getYPosition(price) {
      // How far the price is on the scale from minPrice to maxPrice
      var scale = (price - minPrice) / (maxPrice - minPrice);
      // Calculate y position based on the scale
      return totalHeight - (scale * totalHeight);
  }

  prices.forEach(price => {
      var x = 20;
      var yPos = getYPosition(price);
 
      addLine("", x, yPos, x+200, yPos, "green", 3);
      x = x + 100;
  });
      </script>
<svg id="main" xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="220"></svg>


Solution

  • This is a matter of getting the mapping right from your "world" coordinates to view coordinates.

    I would suggest defining all the "magical numbers" -- that relate to coordinates on the web page -- to constants, so they have a meaningful name.

    Here is a possible implementation:

    // Define the "magic numbers" as constants:
    const labelLeft = 20, labelTop = 55, 
          graphLeft = 90, graphTop = 50.5,
          graphWidth = window.innerWidth - graphLeft - 80,
          graphStep = 30, numLines = 5;
    
    function addText(x, y, t) {
        const text = document.createElementNS('http://www.w3.org/2000/svg','text');
        text.setAttribute('x', x);     
        text.setAttribute('y', y); 
        text.setAttribute('fill', 'black');
        const textNode = document.createTextNode(t);
        text.appendChild(textNode);
        document.getElementById('main').appendChild(text);
    }
     
    function addLine(id, x1, y1, x2, y2, sc, sw) {
        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('id', id);
        line.setAttribute('x1', x1);
        line.setAttribute('y1', y1);
        line.setAttribute('x2', x2);
        line.setAttribute('y2', y2);
        line.setAttribute('stroke', sc);
        line.setAttribute('stroke-width', sw);
        document.getElementById("main").appendChild(line);
    }
      
    function createMapping(prices) {
        const minPrice = Math.min(...prices);
        const maxPrice = Math.max(...prices);
        const step = (maxPrice - minPrice).toFixed(0).replace(/\B./g, "0") / (numLines - 1);
        let topPrice = (maxPrice + minPrice + step) / 2 + step * (numLines >> 1);
        return [topPrice - topPrice % step, step];
    }
    
    function drawLabelsAndLines(topPrice, step) {
        for (let i = 0; i < numLines; i++) {
            const y = graphTop + i * graphStep;
            addText(labelLeft, labelTop + i * graphStep, topPrice - i * step);
            addLine("horizontalLine"+(i+1), graphLeft, y, graphLeft + graphWidth, y, "black", 0.5);
        }
    }
    
    function drawValues(getYPosition, prices) {
        let x = graphLeft;
        let yPos = getYPosition(prices[0]);
        let stepX = graphWidth / (prices.length - 1);
        for (const price of prices.slice(1)) {
            addLine("", x, yPos, x += stepX, yPos = getYPosition(price), "green", 3);
        }
    }
    
    function drawGraph(prices) {
        const [topPrice, step] = createMapping(prices);
        drawLabelsAndLines(topPrice, step);
        drawValues(price => graphTop + (topPrice - price) / step * graphStep, prices);
    }
    
    // demo
    const prices = [28990, 30240, 30890, 32162, 31000, 31820, 31770, 30080, 28340, 28620, 28640, 27930, 28850];
    drawGraph(prices);
    <svg id="main" xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="220"></svg>