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

标签 highcharts tooltip renderer

我们的想法是绘制一个宽度不规则的瀑布图。我们通过将矩形渲染到相应的数据点来实现这种图表样式(出于演示目的,在 fiddle 中可见)。
此外,我们想添加一个工具提示,并让这个工具提示跟随鼠标。

我们面临三个问题:

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

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

  3. 我们将自定义工具提示渲染到渲染器组(矩形、dataLabels)并通过 mouseover 和 mouseout 事件显示它们。第一个问题是悬停数据标签时工具提示消失了。我们做了一个解决方法(第 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
    });

});

});

我们知道这是一个相当复杂的例子,但希望你们提出的所有想法。谢谢!

最佳答案

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

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

  2. 问题:个别边框和矩形不匹配;解决方案:再次四舍五入就可以了

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

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

关于Highcharts 自定义渲染器图表和工具提示,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20002162/

相关文章:

javascript - 在包含大量 HighCharts 的页面上,一个特定的图表未显示。

javascript - 模板助手没有准备好,尽管它应该准备好

html - 将工具提示添加到 SVG 矩形标签

java - 如何制作一个JList,其中每个项目都包含JCheckBox和JLabel,并在单击时具有不同的事件

android - android studio(Android Studio 1.2)布局预览渲染问题

graph - highchart更改单个类别中单个条的颜色

javascript - 如何去除实体压力表下方的空白空间?

javascript - 高效的 HTML 工具提示渲染(重用工具提示)

java - 将鼠标悬停在 JFace TreeViewer 中的一个单元格上时是否可以获得工具提示

java - JTable 中的单选按钮无法正常工作