Highcharts

时间:2018-04-24 12:17:19

标签: javascript jquery highcharts

我正在努力解决显示每个x包含最大值的额外系列的问题。像这样:

Expected result

我能够解析所有系列以聚合点,并找到交叉点以包含它们。目前我的问题是交叉点(A)低于其中一个系列。 (小提琴中的解决方案A)我正在考虑计算线并检查点A是否属于该线。这将解决方案,但会导致B点的问题,它不属于计算线,但应包括在系列中。

有人能指出我正确的方向吗?

    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 :(得分:2)

我试图用稍微不同的方法来解决这个问题。它的要点是:

  1. 查找具有最高点的系列
  2. 找到具有下一个最高点的系列
  3. 检查这两个系列的行之间是否有交叉
  4. 如果有相交,请将其添加为单独的点
  5. 添加下一个最高点
  6. 这样做有一点需要注意,稀疏系列有能力破坏图形,如下所示:https://jsfiddle.net/ewolden/3koe86sx/15/ 解决这个问题会很繁琐,要求你找到所有的x点,对于没有所有点的线,要填写它们的值。

    话虽如此,只要该系列的点数大致相同,这应该会很好。

    &#13;
    &#13;
    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
      })
    })
    &#13;
    <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>
    &#13;
    &#13;
    &#13;

    jsfiddle示例: https://jsfiddle.net/ewolden/3koe86sx/14/