Highcharts自定义渲染器图表和工具提示

时间:2013-11-15 13:25:53

标签: highcharts tooltip renderer

我们的想法是绘制一个宽度不规则的瀑布图。我们通过将矩形渲染到相应的数据点(为了演示目的而在小提琴中可见)来实现此图表样式 此外,我们想要添加工具提示并使用鼠标进行此工具提示。

我们面临三个问题:

  1. 当您非常接近图表时,您会看到rect2和rect3以及rect3和rect4在矩形边缘之间显示小间隙。这似乎很奇怪,因为所有的矩形都是通过相同的for循环程序创建的(小提琴中的第68-84行)。有任何想法吗? (如果更改图表宽度,间隙可能会消失或发生在其他矩形之间......)

  2. 对于第一个和最后一个矩形,我们想要创建单独的边框。因此,我们为第一个和最后一个矩形设置了白色边框(小提琴中的第97,155行)并且之后添加了我们的渲染器路径(虚线,实线)(第221-298行)。正如您在rect0的情况下所看到的那样,即使我们使用与矩形相同的绘图坐标,垂直线也不会完全覆盖白色边框。 (如果更改图表宽度,问题的严重程度会变得更好甚至更差)

  3. 我们为渲染器组(rectangulars,dataLabels)渲染了自定义工具提示,并通过mouseover和mouseout事件显示这些工具提示。第一个问题是当悬停dataLabel时工具提示消失了。我们做了一个解决方法(第190-195行),但我们想知道是否有更优雅的方式在rects和labels上显示工具提示。此外,我们希望使工具提示遵循鼠标移动(事件鼠标移动),但我们无法使用此事件来处理我们的示例。

  4. 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
        });
    
    });
    
    });
    

    我们知道这是一个相当复杂的例子,但我会很感激所有想法。谢谢!

1 个答案:

答案 0 :(得分:9)

现在我们有一个工作版本(thx Pawel !!!):

  1. 问题:有些矩形没有连接; 解决方案:在使用它们进行计算之前,必须对所有plotX和plotY坐标进行舍入。

  2. 问题:个别边界和矩形的错配;解决方案:再次舍入做了诀窍

  3. 问题:a)mousemove用于自定义渲染工具提示b)在悬停事件上绑定工具提示标签和矩形;解决方案:a)拒绝自定义工具提示的想法,而不是在悬停事件上绑定相应数据点的highcharts工具提示为矩形b)为每个矩形创建一个鬼(完全透明)并在其上绑定悬停事件

    //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