Серия максимальных значений в Highcharts - PullRequest
0 голосов
/ 24 апреля 2018

Я борюсь с проблемой показа дополнительной серии, которая будет содержать максимальные значения для каждого х.Например:

Expected result

Мне удалось проанализировать все ряды для агрегирования точек, а также найти точки пересечения для их включения.В настоящее время у меня проблема с точкой пересечения (A), которая находится ниже одной из серий.(Решение A в скрипке) Я думал о вычислении линии и проверке, принадлежит ли точка A этой линии.Это исправит сценарий, но вызовет проблему для точки B bc, она не принадлежит расчетной линии, но должна быть включена в серию.

Может ли кто-нибудь указать мне правильное направление?

    var chart = Highcharts.chart('container', {
        chart: {
             type: 'area',
             zoomType: 'x',
                    backgroundColor:null,
        },
        xAxis: {
           type: 'datetime',
           dateTimeLabelFormats: {
             month: '%b %e',
             year: '%b'
           },
         },
        plotOptions: {
          series: {
            fillOpacity: 0.1,
            marker: {
              enabled: false
            }
          }
        }
    });
    
    chart.addSeries({"id":0,"connectNulls":true,"data":[[1524469020000,30],[1524469080000,30],[1524469140000,30],[1524469200000,30],[1524469260000,30],[1524469320000,30],[1524469380000,30],[1524469440000,30],[1524469500000,30],[1524469560000,58],[1524469620000,4],[1524469680000,4],[1524469740000,4],[1524469800000,4],[1524469860000,4],[1524469920000,4],[1524469980000,4],[1524470040000,4],[1524470100000,4],[1524470160000,4],[1524470220000,4],[1524470280000,4],[1524470340000,4],[1524470400000,4],[1524470460000,4],[1524470520000,22],[1524470580000,22],[1524470640000,22],[1524470700000,22]],"name":"Serie A","color":"#30e430","yAxis":0});
    
    chart.addSeries({"id":1,"connectNulls":true,"data":[[1524469020000,35],[1524469080000,35],[1524469140000,35],[1524469200000,35],[1524469260000,35],[1524469320000,35],[1524469380000,35],[1524469440000,35],[1524469500000,35],[1524469560000,25],[1524469620000,25],[1524469680000,25],[1524469740000,25],[1524469800000,25],[1524469860000,25],[1524469920000,25],[1524469980000,59],[1524470040000,59],[1524470100000,59],[1524470160000,59],[1524470220000,59],[1524470280000,59],[1524470340000,59],[1524470400000,59],[1524470460000,59],[1524470520000,59],[1524470580000,59],[1524470640000,59],[1524470700000,59]],"name":"Serie B","color":"#0cb5ed","yAxis":0});
    
    chart.addSeries({"id":2,"connectNulls":true,"data":[[1524469020000,18],[1524469080000,18],[1524469140000,18],[1524469200000,18],[1524469260000,18],[1524469320000,18],[1524469380000,18],[1524469440000,18],[1524469500000,18],[1524469560000,18],[1524469620000,18],[1524469680000,18],[1524469740000,18],[1524469800000,18],[1524469860000,18],[1524469920000,18],[1524469980000,18],[1524470040000,18],[1524470100000,18],[1524470160000,18],[1524470220000,18],[1524470280000,18],[1524470340000,18],[1524470400000,18],[1524470460000,18],[1524470520000,18],[1524470580000,18],[1524470640000,18],[1524470700000,18]],"name":"Serie C","color":"#e8ad23","yAxis":0});
    
    $('#button').click(function () {
      var results = getChartPointValues();
    
      var data = prepareSummarySeries(results);
    	
      //clean previously added virutal series
      for(var i in chart.series){
        var serie = chart.series[i];
    
        if(serie.userOptions.is_virtual == true)
          serie.remove();
      }
    
      chart.addSeries({
        id: 'virtual_max_b',
        is_virtual: true,
        'connectNulls': true,
        'data' : data.max_b,
        'name' : 'Solution A',
        'color' : '#000',
        'yAxis': 0,
      });
      chart.addSeries({
        id: 'virtual_max_a',
        is_virtual: true,
        'connectNulls': true,
        'data' : data.max_a,
        'name' : 'Base Solution',
        'color' : '#ff0000',
        'yAxis': 0,
      });
    });
    /*
    * Calculate max values for every point
    */
    var prepareSummarySeries = function(data){
    	var tmp_keys = Object.keys(data); ///sort
      tmp_keys = tmp_keys.sort();
      var counter = tmp_keys.length;
      var results = {
      	'max_a': [],
        'max_b': [],
      };
       for(var k = 0; k < counter; k++){
         var key = tmp_keys[k];
         var x_pos = parseFloat(key);
         
         if(x_pos % 1 !== 0)
         {
           var prev_point    = results.max_b.slice(-1)[0];
           var current_point = [x_pos, data[key][0]];
           var next_point    = [ parseFloat(tmp_keys[k+1]), Math.max.apply(null, data[tmp_keys[k+1]] )];
    
           if( checkIfPointBelongsToChart(prev_point, current_point, next_point) ){
             results.max_b.push([ x_pos, current_point[1] ]);
           }
         } else {
         		results.max_b.push([ x_pos, Math.max.apply(null, data[key]) ]);
         }
        
         
         
         results.max_a.push([ x_pos, Math.max.apply(null, data[key]) ]);
      }
      
      return results;
    };
    
    
    var get_line_intersection = function(p0,p1,p2,p3){
      var p0_x = p0.x;
      var p0_y = p0.y;
      var p1_x = p1.x;
      var p1_y = p1.y;
      var p2_x = p2.x;
      var p2_y = p2.y;
      var p3_x = p3.x;
      var p3_y = p3.y;
    
      var s1_x, s1_y, s2_x, s2_y;
      s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
      s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;
    
      var s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
      var t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
    
      if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
      {
        return [p0_x + (t * s1_x),p0_y + (t * s1_y)];
      }
    
      return false;
    };
    
    var checkIfPointBelongsToChart = function(prev_point, current_point, next_point)
    {
      var slope = (next_point[1] - prev_point[1]) / (next_point[0] - prev_point[0]);
      var b = prev_point[1] - slope * prev_point[0];
    
      var tmp_y = slope * current_point[0] + b;
    
      return (tmp_y == current_point[1])? true : false;
    };
    
    // create array with every x point with every possible y value
    // im not taking only max here bc later we could need min instead of max
    var getChartPointValues = function(){
      var results = {}
      var self = this;
      var points = [];
    
      var checked_series = [];
      for(var k =0; k < chart.series.length; k++)
      {
        var entry = chart.series[k];
    
        if(entry.userOptions.is_virtual == true || entry.visible == false)
          continue;
    
        var s1_points = entry.data;
        var c1 = s1_points.length;
    
    
        //add all points from serie
        for(var i = 0; i < c1; i++)
        {
          if(s1_points[i] == undefined || !s1_points[i].isInside)
            continue;
    
          if(points[s1_points[i].x] == undefined){
            points[s1_points[i].x] = [];
          }
    
          points[s1_points[i].x].push(s1_points[i].y);
        }
    
    
        //check all points in all charts for crossing points
        for(var s in chart.series){
          var serie = chart.series[s];
    
          if(serie.userOptions.is_virtual == true || serie.visible == false)
            continue;
    
          //skip serie if combination of series was already checked
          var current_check = entry.userOptions.id + '_' + serie.userOptions.id;
          if(checked_series.indexOf(current_check) != -1 || serie.userOptions.is_virtual == true)
            continue;
    
          checked_series.push(current_check);
          checked_series.push(serie.userOptions.id + '_' + entry.userOptions.id);
    
          if(serie.index != entry.index){
            var s2_points = serie.data;
            var c2 = s2_points.length;
    
            for(var i = 0; i < c1; i++)
            {
              if(s1_points[i] == undefined || !s1_points[i].isInside)
                continue;
    
              for(var j = 0; j < c2; j++)
              {
                if(s2_points[j] == undefined || !s2_points[j].isInside)
                  continue;
    
                var cross = [];
                if(s1_points[i-1] != undefined && s2_points[j-1] != undefined){
                  if(cross = get_line_intersection(s1_points[i-1], s1_points[i], s2_points[j-1], s2_points[j])){
                    if(points[cross[0]] == undefined){
                      points[cross[0]] = [];
                    }
    
                    points[cross[0]].push(cross[1])
                  }
                }
              }
            }
          }
        };
      };
    
      return points;
    }
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>

<button id="button">Add Maximum series</button>
<div id="container" style="height: 400px"></div>

jsFiddle

1 Ответ

0 голосов
/ 25 апреля 2018

Я пытался решить эту проблему, используя немного другой метод. Суть этого:

  1. Найдите серию с максимальной точкой
  2. Найдите серию, которая имеет следующую максимальную точку
  3. Проверьте, есть ли пересечение между линиями этих двух серий
  4. Если есть пересечение, добавьте это как отдельную точку
  5. Добавить следующую максимальную точку

Есть одно предостережение, когда вы делаете это так, разреженный ряд может испортить график, например: https://jsfiddle.net/ewolden/3koe86sx/15/ Решение этой проблемы было бы утомительным, требуя от вас найти все точки х, а для линий без всех точек введите их значения.

Это, как говорится, до тех пор, пока ряды имеют примерно одинаковое количество точек, это должно работать хорошо.

var get_line_intersection = function(p0, p1, p2, p3) {
  var p0_x = p0.x;
  var p0_y = p0.y;
  var p1_x = p1.x;
  var p1_y = p1.y;
  var p2_x = p2.x;
  var p2_y = p2.y;
  var p3_x = p3.x;
  var p3_y = p3.y;

  var s1_x, s1_y, s2_x, s2_y;
  s1_x = p1_x - p0_x;
  s1_y = p1_y - p0_y;
  s2_x = p3_x - p2_x;
  s2_y = p3_y - p2_y;

  var s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
  var t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

  if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
    return [p0_x + (t * s1_x), p0_y + (t * s1_y)];
  }

  return false;
};

//Gets the next point
function getNextPoint(series, current_x, current_y) {
  nextPoint = {
    next_x: 0,
    next_y: 0,
    x: 0,
    y: -1,
    prev_x: 0,
    prev_y: 0
  }
  for (var i = 0; i < series.length; i++) {
    for (var j = 1; j < series[i].length; j++) {
      if (series[i][j].x > current_x) { //Goes one step past current timestamp
        if (series[i][j].y > nextPoint.y || nextPoint.y == -1) { //Checks that this is the max number, could be changed to find min as well
          if (j < series[i].length - 1) { //Checks if this is the last point (to avoid going past last index)
            nextPoint = {
              next_x: series[i][j + 1].x,
              next_y: series[i][j + 1].y,
              x: series[i][j].x,
              y: series[i][j].y,
              prev_x: series[i][j - 1].x,
              prev_y: series[i][j - 1].y,
            }
          } else {
            nextPoint = {
              x: series[i][j].x,
              y: series[i][j].y,
              prev_x: series[i][j - 1].x,
              prev_y: series[i][j - 1].y,
            }
          }
        }
        break;
      }
    }
  }
  return nextPoint
}

function getAllSeries(chart) {
  var allSeries = []
  for (var i = 0; i < chart.series.length; i++) {
    allSeries.push(chart.series[i].data)
  }
  return allSeries
}


var chart = Highcharts.chart('container', {
  chart: {
    type: 'area',
    zoomType: 'x',
    backgroundColor: null,
  },
  xAxis: {
    type: 'datetime',
    dateTimeLabelFormats: {
      month: '%b %e',
      year: '%b'
    },
  },
  plotOptions: {
    series: {
      fillOpacity: 0.1,
      marker: {
        enabled: false
      }
    }
  }
});

chart.addSeries({
  "id": 0,
  "connectNulls": true,
  "data": [
    [1524469020000, 30],
    [1524469080000, 30],
    [1524469140000, 30],
    [1524469200000, 30],
    [1524469260000, 30],
    [1524469320000, 30],
    [1524469380000, 30],
    [1524469440000, 30],
    [1524469500000, 30],
    [1524469560000, 58],
    [1524469620000, 4],
    [1524469680000, 4],
    [1524469740000, 4],
    [1524469800000, 4],
    [1524469860000, 4],
    [1524469920000, 4],
    [1524469980000, 4],
    [1524470040000, 4],
    [1524470100000, 4],
    [1524470160000, 4],
    [1524470220000, 4],
    [1524470280000, 4],
    [1524470340000, 4],
    [1524470400000, 4],
    [1524470460000, 4],
    [1524470520000, 22],
    [1524470580000, 22],
    [1524470640000, 22],
    [1524470700000, 22]
  ],
  "name": "Serie A",
  "color": "#30e430",
  "yAxis": 0
});

chart.addSeries({
  "id": 1,
  "connectNulls": true,
  "data": [
    [1524469020000, 35],
    [1524469080000, 35],
    [1524469140000, 35],
    [1524469200000, 35],
    [1524469260000, 35],
    [1524469320000, 35],
    [1524469380000, 35],
    [1524469440000, 35],
    [1524469500000, 35],
    [1524469560000, 25],
    [1524469620000, 25],
    [1524469680000, 25],
    [1524469740000, 25],
    [1524469800000, 25],
    [1524469860000, 25],
    [1524469920000, 25],
    [1524469980000, 59],
    [1524470040000, 59],
    [1524470100000, 59],
    [1524470160000, 59],
    [1524470220000, 59],
    [1524470280000, 59],
    [1524470340000, 59],
    [1524470400000, 59],
    [1524470460000, 59],
    [1524470520000, 59],
    [1524470580000, 59],
    [1524470640000, 59],
    [1524470700000, 59]
  ],
  "name": "Serie B",
  "color": "#0cb5ed",
  "yAxis": 0
});

chart.addSeries({
  "id": 2,
  "connectNulls": true,
  "data": [
    [1524469020000, 18],
    [1524469080000, 18],
    [1524469140000, 18],
    [1524469200000, 18],
    [1524469260000, 18],
    [1524469320000, 18],
    [1524469380000, 18],
    [1524469440000, 18],
    [1524469500000, 18],
    [1524469560000, 18],
    [1524469620000, 18],
    [1524469680000, 18],
    [1524469740000, 18],
    [1524469800000, 18],
    [1524469860000, 18],
    [1524469920000, 18],
    [1524469980000, 18],
    [1524470040000, 18],
    [1524470100000, 18],
    [1524470130000, 80],
    [1524470160000, 18],
    [1524470220000, 18],
    [1524470280000, 18],
    [1524470340000, 18],
    [1524470400000, 18],
    [1524470460000, 18],
    [1524470520000, 18],
    [1524470580000, 18],
    [1524470640000, 18],
    [1524470700000, 18]
  ],
  "name": "Serie C",
  "color": "#e8ad23",
  "yAxis": 0
});



$('#button').click(function() {
  series = getAllSeries(chart)

  var currentPoint = {
    next_x: 0,
    next_y: 0,
    x: -1,
    y: 0,
    prev_x: 0,
    prev_y: 0
  }
  var max_x = 0;

  //finds the first point
  for (var i = 0; i < series.length; i++) {
    if (currentPoint.y < series[i][0].y || currentPoint.x == -1) { //makes sure this is the largest point
      currentPoint = {
        prev_x: series[i][0].x,
        prev_y: series[i][0].y,
        x: series[i][0].x,
        y: series[i][0].y,
        next_x: series[i][1].x,
        next_y: series[i][1].y
      }
    }
    if (max_x < series[i][series[i].length - 1].x) {
      max_x = series[i][series[i].length - 1].x;
    }
  }


  result = []
  result.push({ //since the first point comes from the above code, we need to add it explicitly
    x: currentPoint.x,
    y: currentPoint.y
  })
  while (currentPoint.x != max_x) { //loop through all points
    nextPoint = getNextPoint(series, currentPoint.x, currentPoint.y);
    let intersect = get_line_intersection({
      x: nextPoint.prev_x,
      y: nextPoint.prev_y
    }, {
      x: nextPoint.x,
      y: nextPoint.y
    }, {
      x: currentPoint.x,
      y: currentPoint.y
    }, {
      x: currentPoint.next_x,
      y: currentPoint.next_y
    })
    if (intersect != false) { //if there is an intersect point, make sure to add it
      result.push({
        x: intersect[0],
        y: intersect[1]
      })
    }
    result.push({
      x: nextPoint.x,
      y: nextPoint.y
    });
    currentPoint = nextPoint
  }
  chart.addSeries({
    name: 'Max Points',
    lineColor: 'red',
    //dashStyle: 'LongDash',
    data: result
  })
})
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>

<button id="button">Add Maximum series</button>
<div id="container" style="height: 400px"></div>

Пример jsfiddle: https://jsfiddle.net/ewolden/3koe86sx/14/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...