Highcharts自定义渲染器图表和工具提示 [英] Highcharts custom renderer chart and tooltip

查看:139
本文介绍了Highcharts自定义渲染器图表和工具提示的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们的想法是绘制宽度不规则的瀑布图。我们通过将矩形渲染到相应的数据点(为演示目的而在小提琴中可见)来实现这种图表风格。

此外,我们希望添加工具提示并使该工具提示跟随鼠标。



我们面临三个问题:


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


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

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

这是我们的小提琴示例

  $(function(){


var chart = new Highcharts.Chart({
chart:{
renderTo:'container',
type:'scatter'
},
title:{
text:'不等宽的自定义瀑布'
},
xAxis:{
min:0,
max:50,
颠倒过来:true
},
yAxis:{
title:{
b $ b text:'分享'
},
分钟:0,
最多:100
},
工具提示:{
启用:false
},
图例:{
启用:false
},
学分:{
启用:false
},
系列:[{
名称:'basicData',
visible:true,//用于演示目的
data:[
[50,40],
[45,48],
[39,52],
[33,68],
[22,75],
[15,89],
[5,100]
]
}]
},
//为自定义渲染器添加函数
函数(图表){

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 = [];

//所有矩形的渲染器组
rectGroup = chart.renderer.g()
.attr({
zIndex:5
})
.add();

//为每个点绘制一个矩形
for(var i = 0; i< points.length; i ++){

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

if(i === 0){//第一个矩形高度由yAxis和yValue的像素差值定义
height = yZero - points [i] .plotY
} else {// else height是yValue和前面的像素差值yValue
height = points [i - 1] .plotY - points [i] .plotY
};
if(i === points.length - 1){//用于xValue和xAxis在点= 0的最后一个矩形像素差异
width = this.xAxis [0] .translate(0) - points [i] .plotX
} else {// else xValue和随后的xValue的像素差异
width = points [i + 1] .plotX - points [i] .plotX
} ;

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

// rects的一般样式,第一个和最后一个rect的例外
var attrOptions;
if(i === 0){
attrOptions = {
id:i,
'stroke-width':0.75,
stroke:'rgb(255 ,255,255)',//白色边框,稍后由虚线覆盖
fill:{
linearGradient:{
x1:1,
y1:0,
x2:0,
2: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,
笔画:'rgb(255,255,255)',//白色边框,稍后由虚线覆盖
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]
};
}

//绘制矩形,y-position被设置为yAxis用于动画
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

},{
期限:1000
});
}; // for循环遍历所有rect


//渲染器居中dataLabels到矩形
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。 ('rect'+ i)
.attr({
align:'center',
zIndex:5,
padding:0
})
.css({
fontSize:'11px',
color:labelColor
})
.add(rectGroup);

var labelBBox = label.getBBox();
$ b $ label.attr({
x:xAll [i] + widthAll [i] * 0.5,
y:yAll [i] + heightAll [i] * 0.5 - labelBBox.height * 0.5
});
}; //循环为dataLabels结束


//将工具提示添加到矩形和标签(rectGroup)
var tooltipIndex;

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

//获取活动元素(还是有更简单的方法?)
var el =(e.target.correspondingUseElement)?e.target.correspondingUseElement:e.target;

//用这个元素属于哪个dataPoint的'id'确定
//问题:if如果(!isNaN(i)){
tooltipIndex = i; $ b())
var e =
$ b //根据rect
text = chart.renderer.text('这可能是一个信息文本',xAll [tooltipIndex],yAll [ tooltipIndex] - 30)
.attr({
zIndex:101
})
.add();

var box = text.getBBox( );
//围绕工具提示文本的框
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();
} )


//将第一个和最后一个矩形渲染为开放并且部分为点矩形
var M ='M',
L ='L',
pathStartSol = [],
pathEndSol = [],
pathStartDot = [],
pathEndDot = [],
y0 = this.chartHeight - this.yAxis [0]。底部,
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], //不完全匹配矩形
的底层白色边框L,xAll [1],y0,//不完全匹配矩形
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
});

});

});

我们知道这是一个相当复杂的例子,但会感谢所有提供给您们的想法。

解决方案

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


  1. 问题:某些矩形未连接;
    解决方案:所有plotX和plotY坐标必须先进行四舍五入,然后再进行计算。


  2. 问题:单个边界和矩形的错配;解决方案:再次四舍五入的做法


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

      //为每个矩形绘制幻象,并绑定它的highcharts工具提示
    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();
    });


    };

    这里是工作小提琴



Our idea was to draw a waterfall chart with unregular widths. We achieved this chart style by rendering rectangulars to the corresponding data points (visible in fiddle for demonstration purposes).
Moreover, we want to add a tooltip and make this tooltip following the mouse.

We are facing three problems:

  1. When you zoom very close to the chart, you will see that namely rect2 and rect3 as well as rect3 and rect4 show small gaps between the rectangulars edges. This seems to be strange because all rectangulars have been created by the same for-loop procedure (lines 68-84 in fiddle). Any ideas? (If you change the chart width, the gaps can vanish or occur between other rectangulars...)

  2. For the first and last rectangular we wanted to create individual borders. Therefore, we set up white borders to the first and last rectangulars (lines 97,155 in fiddle) and added our renderer paths (dotted, solid) lines afterwards (lines 221-298). As you can see in case of rect0 the vertical line does not cover the white border exactly, even though we used the same plot coordinates as the rectangular has. (If you change the chart width, the magnitude of the problem gets better or even worse)

  3. We rendered custom tooltips to the renderer group (rectangulars, dataLabels) and display these by mouseover and mouseout events. A first problem was that the tooltip disappeared when hovering the dataLabel. We made a workaround (lines 190-195) but we are wondering if there is a more elegant way to display the tooltip on both rects and labels. Further, we want to make the tooltip follow the mouse movements (event mousemove) but we can't get this event to work on our example.

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

});

});

We know it's a rather complex example but would appreciate all ideas that come up to you guys. Thanks!

解决方案

Now we have a working version (thx Pawel!!!):

  1. problem: some rectangulars are not connected; solution: all plotX and plotY coordinates have to be rounded before you do calculations with them.

  2. problem: missmatch of individual borders and rectangulars; solution: again rounding did the trick

  3. problem: a) mousemove for custom rendered tooltip b) bind tooltip on hover event for label and rectangular; solution: a) reject custom tooltip idea instead bind highcharts tooltip of corresponding data point on hover event for rectangular b) create a ghost (completely transparent) for each rectangular and bind hover event on it

    //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自定义渲染器图表和工具提示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆