2013-11-15 4 views
3

Наша идея состояла в том, чтобы нарисовать диаграмму водопада с нерегулярной шириной. Мы достигли этого стиля диаграммы путем рендеринга прямоугольников в соответствующие точки данных (видимые в скрипке для демонстрационных целей).
Кроме того, мы хотим добавить всплывающую подсказку и сделать эту подсказку после мыши.Highcharts custom renderer chart и tooltip

Мы столкнулись с тремя проблем:

  1. При масштабировании очень близко к графику, вы увидите, что именно rect2 и rect3, а также rect3 и rect4 показывают небольшие зазоры между краями прямоугольников. Это кажется странным, потому что все прямоугольники созданы одной и той же процедурой для цикла (строки 68-84 в скрипке). Есть идеи? (Если вы измените ширину диаграммы, зазоры могут исчезнуть или произойти между другими прямоугольниками ...)

  2. Для первого и последнего прямоугольников мы хотели создать отдельные границы. Поэтому мы устанавливаем белые границы для первого и последнего прямоугольников (строки 97,155 в скрипке) и затем добавляем наши пути визуализации (пунктирные, сплошные) строки (строки 221-298). Как вы можете видеть, в случае rect0 вертикальная линия точно не покрывает белую границу, хотя мы использовали те же графические координаты, что и прямоугольная. (Если изменить ширину графика, величина проблемы становится лучше или еще хуже)

  3. Мы оказаны таможенными подсказки к визуализатору группе (прямоугольники, dataLabels) и отображать их на наведении мышей и MouseOut событий. Первая проблема заключалась в том, что всплывающая подсказка исчезла при зависании dataLabel. Мы сделали обходное решение (строки 190-195), но нам интересно, есть ли более элегантный способ отображения всплывающей подсказки на обоих прямоугольниках и ярлыках. Кроме того, мы хотим сделать подсказку движением мыши (событие mousemove), но мы не можем заставить это событие работать над нашим примером.

Here is our fiddle example

$(function() { 


var chart = new Highcharts.Chart({ 
    chart: { 
     renderTo: 'container', 
     type: 'scatter' 
    }, 
    title: { 
     text: 'Custom waterfall with unequal width' 
    }, 
    xAxis: { 
     min: 0, 
     max: 50, 
     reversed: true 
    }, 
    yAxis: { 
     title: { 
      text: 'Share' 
     }, 
     min: 0, 
     max: 100 
    }, 
    tooltip: { 
     enabled: false 
    }, 
    legend: { 
     enabled: false 
    }, 
    credits: { 
     enabled: false 
    }, 
    series: [{ 
     name: 'basicData', 
     visible: true, //for demonstration purpose 
     data: [ 
      [50, 40], 
      [45, 48], 
      [39, 52], 
      [33, 68], 
      [22, 75], 
      [15, 89], 
      [5, 100] 
     ] 
    }] 
}, 
//add function for custom renderer 
function (chart) { 

    var points = this.series[0].data, 
     addMarginX = this.plotLeft, 
     addMarginY = this.plotTop, 
     xZero = this.series[0].points[0].plotX, 
     yZero = this.chartHeight - addMarginY - this.yAxis[0].bottom, 
     xAll = [], 
     yAll = [], 
     widthAll = [], 
     heightAll = []; 

    //renderer group for all rectangulars 
    rectGroup = chart.renderer.g() 
     .attr({ 
     zIndex: 5 
    }) 
     .add(); 

    //draw for each point a rectangular 
    for (var i = 0; i < points.length; i++) { 

     var x = points[i].plotX + addMarginX, 
      y = points[i].plotY + addMarginY, 
      width, 
      height; 

     if (i === 0) { //for the first rect height is defined by pixel difference of yAxis and yValue 
      height = yZero - points[i].plotY 
     } else { // else height is pixel difference of yValue and preceeding yValue 
      height = points[i - 1].plotY - points[i].plotY 
     }; 
     if (i === points.length - 1) { // for the last rectangular pixel difference of xValue and xAxis at point=0 
      width = this.xAxis[0].translate(0) - points[i].plotX 
     } else { // else pixel difference of xValue and subsequent xValue 
      width = points[i + 1].plotX - points[i].plotX 
     }; 

     xAll.push(x); 
     yAll.push(y); 
     widthAll.push(width); 
     heightAll.push(height); 

     //general styling of rects, exception for first and last rect 
     var attrOptions; 
     if (i === 0) { 
      attrOptions = { 
       id: i, 
        'stroke-width': 0.75, 
       stroke: 'rgb(255, 255, 255)', //white border which is later covered by dotted lines 
       fill: { 
        linearGradient: { 
         x1: 1, 
         y1: 0, 
         x2: 0, 
         y2: 0 
        }, 
        stops: [ 
         [0, Highcharts.getOptions().colors[0]], 
         [1, 'rgba(255,255,255,0.5)'] 
        ] 
       } 
      }; 
     } else if (i === points.length - 1) { 
      attrOptions = { 
       id: i, 
        'stroke-width': 0.75, 
       stroke: 'rgb(255, 255, 255)', //white border which is later covered by dotted lines 
       fill: { 
        linearGradient: { 
         x1: 0, 
         y1: 0, 
         x2: 1, 
         y2: 0 
        }, 
        stops: [ 
         [0, Highcharts.getOptions().colors[0]], 
         [1, 'rgba(255,255,255,0.5)'] 
        ] 
       } 
      }; 
     } else { 
      attrOptions = { 
       id: i, 
        'stroke-width': 0.75, 
       stroke: 'black', 
       fill: Highcharts.getOptions().colors[0] 
      }; 
     } 

     // draw rect, y-position is set to yAxis for animation 
     var tempRect = chart.renderer.rect(x, this.chartHeight - this.yAxis[0].bottom, width, 0, 0) 
      .attr(attrOptions) 
      .add(rectGroup); 

     //animate rect 
     tempRect.animate({ 
      y: y, 
      height: height 

     }, { 
      duration: 1000 
     }); 
    }; // for loop ends over all rect 


    //renderer centered dataLabels to rectangulars 
    for (var i = 0; i < points.length; i++) { 

     var labelColor = 'rgb(255,255,255)'; 
     if (i === 0 || i === points.length - 1) { 
      labelColor = '#666666' 
     } 
     var label = chart.renderer.label('rect' + i) 
      .attr({ 
      align: 'center', 
      zIndex: 5, 
      padding: 0 
     }) 
      .css({ 
      fontSize: '11px', 
      color: labelColor 
     }) 
      .add(rectGroup); 

     var labelBBox = label.getBBox(); 

     label.attr({ 
      x: xAll[i] + widthAll[i] * 0.5, 
      y: yAll[i] + heightAll[i] * 0.5 - labelBBox.height * 0.5 
     }); 
    }; // loop for dataLabels ends 


    // add tooltip to rectangulars AND labels (rectGroup) 
    var tooltipIndex; 

    rectGroup.on('mouseover', function (e) { 

     //get the active element (or is there a simpler way?) 
     var el = (e.target.correspondingUseElement) ? e.target.correspondingUseElement : e.target; 

     //determine with the 'id' to which dataPoint this element belongs 
     //problem: if label is hovered, use tootltipIndex of rect 
     var i = parseFloat(el.getAttribute('id')); 
     if (!isNaN(i)) { 
      tooltipIndex = i; 
     } 
     // render text for tooltip based on coordinates of rect 
     text = chart.renderer.text('This could be <br>an informative text', xAll[tooltipIndex], yAll[tooltipIndex] - 30) 
      .attr({ 
      zIndex: 101 
     }) 
      .add(); 

     var box = text.getBBox(); 
     //box surrounding the tool tip text      
     border = chart.renderer.rect(box.x - 5, box.y - 5, box.width + 10, box.height + 10, 5) 
      .attr({ 
      fill: 'rgba(255, 255, 255, 0.95)', 
      stroke: 'blue', 
       'stroke-width': 1, 
      zIndex: 100 
     }) 
      .add(); 
    }) 
     .on('mouseout', function() { 

     text.destroy(); 
     border.destroy(); 
    }) 


    //render first and last rect as open and partly dotted rect 
    var M = 'M', 
     L = 'L', 
     pathStartSol = [], 
     pathEndSol = [], 
     pathStartDot = [], 
     pathEndDot = [], 
     y0 = this.chartHeight - this.yAxis[0].bottom, 
     last = xAll.length - 1; 

    pathStartDot = [ 
    M, xAll[0], y0, 
    L, xAll[0] + widthAll[0] * 0.6, y0, 
    M, xAll[0], y0, 
    L, xAll[0] + widthAll[0] * 0.6, y0, 
    M, xAll[last] + widthAll[last] * 0.4, y0, 
    L, xAll[last] + widthAll[last], y0, 
    M, xAll[last] + widthAll[last] * 0.4, y0, 
    L, xAll[last] + widthAll[last], y0]; 

    pathStartSol = [ 
    M, xAll[0] + widthAll[0] * 0.6, y0, 
    L, xAll[1], y0, 
    L, xAll[1], y0, 
    L, xAll[0] + widthAll[0] * 0.6, y0, 
    M, xAll[last] + widthAll[last] * 0.4, y0, 
    L, xAll[last], y0, 
    L, xAll[last], y0, 
    L, xAll[last] + widthAll[last] * 0.4, y0]; 

    pathEndDot = [ 
    M, xAll[0], yAll[0], 
    L, xAll[0] + widthAll[0] * 0.6, yAll[0], 
    M, xAll[0], y0, 
    L, xAll[0] + widthAll[0] * 0.6, y0, 
    M, xAll[last] + widthAll[last] * 0.4, yAll[last], 
    L, xAll[last] + widthAll[last], yAll[last], 
    M, xAll[last] + widthAll[last] * 0.4, yAll[last - 1], 
    L, xAll[last] + widthAll[last], yAll[last - 1]]; 

    pathEndSol = [ 
    M, xAll[0] + widthAll[0] * 0.6, yAll[0], 
    L, xAll[1], yAll[0], // does not match exactly the underlying white border of rect 
    L, xAll[1], y0, // does not match exactly the underlying white border of rect 
    L, xAll[0] + widthAll[0] * 0.6, y0, 
    M, xAll[last] + widthAll[last] * 0.4, yAll[last], 
    L, xAll[last], yAll[last], 
    L, xAll[last], yAll[last - 1], 
    L, xAll[last] + widthAll[last] * 0.4, yAll[last - 1]]; 

    var pathSol = chart.renderer.path(pathStartSol) 
     .attr({ 
     'stroke-width': 1, 
     stroke: 'black', 
     zIndex: 100 
    }).add(); 

    var pathDot = chart.renderer.path(pathStartDot) 
     .attr({ 
     'stroke-width': 1, 
     stroke: 'black', 
     zIndex: 100, 
     dashstyle: 'Dot' 
    }).add(); 

    pathSol.animate({ 
     d: pathEndSol 
    }, { 
     duration: 1000 
    }); 

    pathDot.animate({ 
     d: pathEndDot 
    }, { 
     duration: 1000 
    }); 

}); 

}); 

Мы знаем, что это довольно сложный пример, но был бы признателен за все идеи, которые приходят к вам, ребята. Благодаря!

+0

Это необходимо, чтобы эта ширина отличалась? С самого начала у вас есть несортированные данные, которые неверны для Highcharts, и когда сортировка применяется, я получаю разрушенную диаграмму. Разве вы не можете использовать простой водопад, поддерживаемый Highcharts?Смотрите: http://www.highcharts.com/demo/ waterfall –

+0

Для этого типа диаграммы необходима разная ширина. Поэтому мы не можем использовать или изменять водопад HS, который основан на диаграмме столбцов. Вот почему мы попытались сделать прямоугольники для достижения какого-то водопада различной ширины ... – user2285607

+0

Итак, в этом случае советую начать с сортировки данных и фиксации, а затем ваших исправлений. После этого, если вы создадите jsFiddle с этим, я постараюсь вам помочь. –

ответ

8

Теперь у нас есть рабочая версия (ТНХ Pawel !!!):

  1. проблема: некоторые прямоугольники не связаны; Решение: все координаты plotX и plotY должны округляться, прежде чем выполнять с ними вычисления.

  2. проблема: недостаток отдельных границ и прямоугольников; Решение: снова округление делало трюк

  3. проблема: a) mousemove для персонализированной всплывающей подсказки b) привязать всплывающую подсказку к событию наведения для метки и прямоугольника; Решение: а) отклонить пользовательские подсказки идеи вместо того, чтобы связать Highcharts подсказки соответствующих точки данных на парение события для прямоугольных б) создают призрак (полностью прозрачным) для каждого прямоугольного и связать событие парения на нем

    //draw ghost for each rectangular and bind tooltip of highcharts on it 
    for (var i = 0; i < points.length; i++) { 
    
        var ghostRect = chart.renderer.rect(xAll[i], yAll[i], widthAll[i], heightAll[i], 0) 
         .attr({ 
         id: i, 
         'stroke-width': 0, 
         stroke: 'rgba(255, 255, 255, 0)', 
         fill: 'rgba(255, 255, 255, 0)', 
         zIndex: 10 
        }) 
         .add() 
         .on('mouseover', function() { 
          var index = parseInt(this.getAttribute('id')); 
          var point = chart.series[0].points[index]; 
          chart.tooltip.refresh(point); 
        }) 
         .on('mouseout', function() { 
          chart.tooltip.hide(); 
        }); 
    
    
    }; 
    

    Here is the working fiddle

+0

Хорошая работа! Возьми мой голос вверх;) –

+0

Отличный материал. Это помогло мне создать мою собственную «диаграмму поверхности». Грубый макет: http://jsfiddle.net/kzoon/JvS3a/ – Koos