/* This notice must be untouched at all times.

This is javascript that calculates a channel around a bar-chart graph
produced by Ragnar Nohre 

rndWalk.js v1.0

The latest version is available at
http://www.ragnarius.com
http://www.ragnarius.com/sw/hokus/index.php
http://www.ragnarius.com/en/hokus/index.php

*/

var deltaX = 6;
var HEIGHT = 5;  // changed by initSize;
var WIDTH  = 5;

var arrVol = new Array( 100 );
var arrMin = new Array( 100 );
var arrMax = new Array( 100 );
var arrIn  = new Array( 100 );
var arrOut = new Array( 100 );

function obj( nam )
{
  return document.getElementById(nam);
}

function createArr( len )
{
  arrVol = new Array( len );
  arrMin = new Array( len );
  arrMax = new Array( len );
  arrIn  = new Array( len );
  arrOut = new Array( len );
}


function initSize()
{
 HEIGHT = obj("barChartCanvas").clientHeight;
 WIDTH  = obj("barChartCanvas").clientWidth;

 var len = parseInt(WIDTH/(2*deltaX))*2;
 createArr( len );
}


function dayChange(vol)
{
  var minSum = 9999;
  var maxSum = -9999;
  var sum    = 0;
  for (i=0;i<vol ; ++i)
   { if (Math.random()>0.5)
          sum++;
     else sum--;
     
     if (sum<minSum) minSum = sum;
     if (sum>maxSum) maxSum = sum;
   }

  this.min = minSum;
  this.max = maxSum;
  this.next = sum;
}

function myDice()
{
  return  parseInt(1 + 6*Math.random() );
}// Dice


function myVol()
{
   var vol = 0;
   for (i=0;i<4; ++i)
      vol += myDice();
   return vol;

}


function calcChartData()
{
   var value = 100;
   var len = arrMin.length;

   for (d=0 ; d<len ; ++d)
     { 
       var vol = myVol();

       var change = new dayChange( vol );
       change.min += value;
       change.max += value;
       change.next += value;


       arrVol[d] = vol;
       arrMin[d] = change.min;
       arrMax[d] = change.max;
       arrIn[d]  = value;
       arrOut[d] = change.next;

       value = change.next;
       if (value<0)
          value = 0;
     }

}//calcChartData



// The arrays arrMin and arrMax should be declared elsewere


    // ****************************************************************
    // ANROP:   y = Extrapolate( x,  x1,y1,  x2,y2);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: Extrapolerar en linje och returnerar y(x)
    // *****************************************************************
    function Extrapolate(x, x1,y1, x2,y2)
    {
	if (x1==x2)
	    return y1;
	var y = (y1-y2)*(x-x1)/(x1-x2) + y1;
	return y;
    }


    // ****************************************************************
    // ANROP:   ix = ixMax(ixStart, ixEnd);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: Returnerar ix, ixStart<=ix<ixEnd,  sådant att
    //          arrMax[ix] är maximalt.
    // *****************************************************************
    function ixMax(ixStart, ixEnd)
    {
	var index=ixStart;
	for (var ix=ixStart ; ix<ixEnd ; ++ix)
	    if (arrMax[ix]>arrMax[index])
		index = ix;
	return index;
    }// ixMax


    // ****************************************************************
    // ANROP:   var ix = ixMin( ixStart, ixEnd);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: Returnerar ix, ixStart<=ix<ixEnd,  sådant att
    //          arrMin[ix] är minimalt.
    // *****************************************************************
    function ixMin(ixStart, ixEnd)
    {
	var index=ixStart;
	for (var ix=ixStart ; ix<ixEnd ; ++ix)
	    if (arrMin[ix]<arrMin[index])
		index = ix;
	return index;
    }//ixMin


    // ****************************************************************
    // ANROP:   ix = ixMaxExtra(ixStart, ixEnd,  x, ixFix);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: För alla ix ixStart<= ix < ixEnd undersökes den linje som
    //          skär punkterna (ix, arrMax[ix]) och (ixFix, arrMax[ixFix]).
    //          Linjens y-värde i punkten x, y(x), beräknas. Index ix för
    //          den linje som har störst y-värde returneras.
    // *****************************************************************
    function ixMaxExtra(ixStart, ixEnd,  x, xFix)
    {
	var index=ixStart;
	var  vindex  = Extrapolate(x,  xFix,arrMax[xFix], index,arrMax[index]);

	for (var ix=ixStart ; ix<ixEnd ; ++ix)
	  { var  v = Extrapolate(x, xFix, arrMax[xFix], ix, arrMax[ix]);
	    if (v>vindex)
	      { vindex = v;
	        index  = ix;
	      }
	  }
	return index;
    }// ixMaxExtra

    function Line( x0,y0,  x1,y1 )
    { this.x0=x0;
      this.y0=y0;
      this.x1=x1;
      this.y1=y1;
    }


    // ****************************************************************
    // ANROP:   line = calcResistance( ixStart, ixEnd);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: beräknar motståndslinjen
    // *****************************************************************
    function calcResistance( ixStart, ixEnd)
    {
	var mid    = (ixStart+ixEnd)/2;
        mid = parseInt( mid );
	var maxLeft=0;
	var maxRight=ixMax(mid, ixEnd);


	for (var ite=0 ; ite<6 ; ++ite)
	  { maxLeft = ixMaxExtra(  ixStart, mid, mid-1, maxRight);
	    maxRight = ixMaxExtra(  mid, ixEnd, mid+1, maxLeft);
	  }

	var x0 	= ixStart * deltaX+5;
	var y0   = Extrapolate(x0,  maxLeft*deltaX+5, arrMax[maxLeft], maxRight*deltaX+5, arrMax[maxRight]);

	var x1 =  (ixEnd-1)*deltaX+5;
 	var y1 =  Extrapolate(x1, maxLeft*deltaX+5, arrMax[maxLeft], maxRight*deltaX+5, arrMax[maxRight]);


	var line = new Line(x0,y0,  x1,y1);
	 line = new Line(x0,y0,  x1,y1);

	return line;


    }// calcResistance



    // ****************************************************************
    // ANROP:   int ix = ixMinExtra( ixStart, ixEnd, x, ixFix);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: För alla ix ixStart<= ix < ixEnd undersökes den linje som
    //          skär punkterna (ix, arrMin[ix]) och (ixFix, arrMin[ixFix]).
    //          Linjens y-värde i punkten x, y(x), beräknas. Index ix för
    //          den linje som har minst y-värde returneras.
    // *****************************************************************
    function ixMinExtra(ixStart,ixEnd,  x,  xFix)
    {
	var index=ixStart;
	var vindex  = Extrapolate(x, xFix, arrMin[xFix], index, arrMin[index]);

	for (var ix=ixStart ; ix<ixEnd ; ++ix)
	  { var v = Extrapolate(x, xFix, arrMin[xFix], ix, arrMin[ix]);
	    if (v<vindex)
	      { vindex = v;
	        index  = ix;
	      }
	  }
	return index;
    }// ixMinExtra






    // ****************************************************************
    // ANROP:   calcSupport( ixStart, ixEnd);
    // VERSION: 6/3 01 RAG
    // UPPGIFT: Bestämmer stoedlinjen
    // *****************************************************************
    function calcSupport( ixStart, ixEnd)
    {
	
	var mid    = (ixStart+ixEnd)/2;
        mid = parseInt( mid );
	var minLeft=0;
	var minRight=ixMin( mid, ixEnd);

	for (var ite=0 ; ite<6 ; ++ite)
	  { minLeft = ixMinExtra( ixStart, mid, mid-1, minRight);
	    minRight = ixMinExtra( mid, ixEnd, mid+1, minLeft);
	  }

	var x0  = ixStart*deltaX + 5;
	var y0  = Extrapolate(x0, minLeft*deltaX+5, arrMin[minLeft], minRight*deltaX+5, arrMin[minRight]);
	var x1  = (ixEnd-1)*deltaX+5
 	var y1  = Extrapolate(x1, minLeft*deltaX+5, arrMin[minLeft], minRight*deltaX+5, arrMin[minRight]);
	var line = new Line(x0,y0,  x1,y1);
	return line;
    }// calcSupport




    // ****************************************************************
    // ANROP:   a = area(supLine, resLine );
    // VERSION: 16/7 04 RAG
    // UPPGIFT: Returnerar kanalens Area
    // *****************************************************************
    function area(supLine, resLine)
    {
      var h0 = resLine.y0-supLine.y0;
      var h1 = resLine.y1-supLine.y1;
      var h  = (h0+h1)/2.0;

      var a = (supLine.x1-supLine.x0)*h;
      return a;
    }


  // **************************************************************
  // ANROP:    a = splitArea( ixSplit, max )
  // VERSION:  6/3 01 RAG
  // UPPGIFT:  Beräknar totala arean av två kanaler om 
  //           trendbrottet sker vid ixSplit
  // **************************************************************
 function splitArea(ixSplit, len )
  {
      var resLeft = calcResistance(0, ixSplit);
      var supLeft = calcSupport(0, ixSplit);

      var resRight = calcResistance(ixSplit, len);
      var supRight = calcSupport(ixSplit, len );

      return area(supLeft, resLeft) + area(supRight, resRight);

  }// splitArea


  // **************************************************************
  // ANROP:    split = optimalSplit( num );
  // VERSION:  6/3 01 RAG
  // UPPGIFT:  Optimerar vänstra och högra kanalen
  // **************************************************************
 function optimalSplit(num )
  {
      var   minI = num/2;
      var   minA = splitArea( minI , num );


      for (var ix=20 ; ix<(num-20) ; ix+=20)
	{ var a = splitArea(ix, num);
	  if (a<minA)
	      { minA = a;
	        minI = ix;
	      }
	}
      // refeine
      var ix0 = minI-20;
      if (ix0<1) ix0=1;
      var ix1 = minI+20;
      if (ix1>num-2) ix1=num-2;

      for (var ix=ix0 ; ix<ix1 ; ix+=5)
	{ var a = splitArea(ix, num);
	  if (a<minA)
	      { minA = a;
	        minI = ix;
	      }
	}

      return minI;
  }// optimalSplit

function drawChannel(graph,  res, sup, color )
{

  graph.setColor( color );
  graph.drawLine(res.x0,HEIGHT-res.y0,  res.x1,HEIGHT-res.y1);
  graph.drawLine(sup.x0,HEIGHT-sup.y0,  sup.x1,HEIGHT-sup.y1);
  graph.paint();
}// drawChannel


function optChannels(len, color)
{
//  var len = arrMin.length;
  var split = optimalSplit( len );

  var resLeft = calcResistance(0, split);
  var supLeft = calcSupport(0, split);

  var resRight = calcResistance(split, len);
  var supRight = calcSupport(split, len)


  var resTot = calcResistance(0,len);
  var supTot = calcSupport(0,len);

  var a1 = area(supTot, resTot);
  var a2 = area(supLeft, resLeft) + area(supRight, resRight);


  if (a1> 1.7*a2)
  {   drawChannel( barGraph, resLeft, supLeft,  color );
      drawChannel( barGraph, resRight, supRight, color );
  }
  else drawChannel( barGraph, resTot, supTot, color)

}// optChannels



function singleChannel(len , color)
{
  var resTot = calcResistance(0,len);
  var supTot = calcSupport(0,len);

  drawChannel( barGraph, resTot, supTot, color ); 
  barGraph.paint();
}


function drawData(bGraph, len)
{
   bGraph.setColor("#666666"); // gray
   for (d=0 ; d<len ; ++d)
     { var x = 5+ d*deltaX;

       var vol = arrVol[d];

       var y = HEIGHT - vol;
       bGraph.fillRect( x-2, y, 4, vol );
     }

   bGraph.paint();

   bGraph.setColor("#0000FF"); // blue
   for (d=0 ; d<len ; ++d)
     { var x = 5+ d*deltaX;
       bGraph.drawLine( x, HEIGHT-arrMin[d], x,HEIGHT-arrMax[d] );
       bGraph.drawLine( x-1, HEIGHT-arrIn[d], x, HEIGHT-arrIn[d]);
       bGraph.drawLine( x,   HEIGHT-arrOut[d], x+1, HEIGHT-arrOut[d]);
     }
   bGraph.paint();

}// drawData


function drawChartData(bGraph, len)
{
   bGraph.setColor("#EEEEEE"); // gray
   bGraph.fillRect( 0,0, WIDTH, HEIGHT);
   bGraph.paint();


   drawData(bGraph, len);

}// drawChartData



function demo1()// no channel
{
  initSize();

  var len = arrMin.length;
  calcChartData();
  drawChartData(barGraph, len);
}// demo1


function demo2() // single channel
{
  initSize();

  var len = arrMin.length;
  calcChartData();
  drawChartData(barGraph, len);
  singleChannel(len, "#00DD00" );
}// demo2


function demo3() // one or two channels
{
  initSize();
  var len = arrMin.length;

  calcChartData();
  drawChartData(barGraph, len);
  optChannels(len, "#00DD00" );

}// barChart





