I need two labels for a column, one above to show the score and one below to show if it is a "test" or a "retest". How do I go about doing it?
I assume complicated calculation is needed, something like this? http://jsfiddle.net/72xym/4/ (but I can't really understand)
I got other diagrams need to achieve this as well. Basically I need to do this:
My code is inside here: http://jsfiddle.net/kscwx139/6/
I did this for the part to add the label:
..., function() {
var i = 0, j = 0, len_i, len_j, self = this, labelBBox, labels=[];
for(i=0; i<this.series.length; i++) {
labels[i] = [];
for(j=0; j<this.series[i].data.length; j++) {
labels[i][j] = this.renderer.label(self.series[i].name)
.css({
width: '100px',
color: '#000000',
fontSize: '12px'
}).attr({
zIndex: 100
}).add();
labelBBox = labels[i][j].getBBox();
labels[i][j].attr({
x: 100, //1) what to put here?
y: 100 //2) what to put here?
});
}
}
}
I would do something like this:
var chart2 = new Highcharts.Chart(options2, function() {
var i = 0, j = 0, len_i, len_j, self = this, labelBBox, labels=[], point;
for(i=0; i<this.series.length; i++) {
labels[i] = [];
for(j=0; j<this.series[i].data.length; j++) {
labels[i][j] = this.renderer.label(self.series[i].name)
.css({
width: '100px',
color: '#000000',
fontSize: '12px'
}).attr({
zIndex: 100,
align: "center"
}).add();
point = this.series[i].data[j];
labelBBox = labels[i][j].getBBox();
labels[i][j].attr({
x: point.plotX + this.plotLeft + point.series.pointXOffset + point.shapeArgs.width / 2,
y: this.plotTop + this.plotHeight - labelBBox.height
});
}
}
});
Demo: http://jsfiddle.net/kscwx139/7/
Note: I would suggest using chart.redraw event to update labels position (label[i][j].animate({ x: ..., y: ... });) when resizing browser, updating data, etc.
Short explanation:
point.plotX - x-position in the plotting area (center of the point's shape)
this.plotLeft - left offset for left yAxis (labels, title)
point.series.pointXOffset - offset for multiple columns in the same category
this.plotTop - the same as plotLeft but from top ;)
this.plotHeight - height of the plotting area
labelBBox.height - height of the label to place it above the xAxis line
Related
I want to place a few pieces of text in my graphic. Now, I can do this manually, by specifying the xy coordinates. But this is probably not the best way, as the size of the graph can change, and then the positions are hard coded.
Is there a way to derive in a dynamical way the positions of a line? That is: What is the x/y coordinate of the position x=1980? Here is a fiddle for it.
options.series.push(series);
options.chart.events = {
load: function() {
var chart = this;
chart.renderer.text('1970: Arbitrary chosen reference year: All cumulative values are relative to 1970.', xOffset, yOffset)
.css({
fontSize: '13px',
color: '#aaa',
width: '550px'
})
.add();
chart.renderer.text('1980: observed loss in glacier thickness cumulates to 2 m w.e. since 1970.', 520, 220)
.css({
fontSize: '13px',
color: '#7994d9',
width: '520px'
})
.add();
chart.renderer.text('1990: cumulative ice loss of 4 m w.e.', 690, 255)
.css({
fontSize: '13px',
color: '#ff6c7f',
width: '320px'
})
.add();
}
Maybe this can help you:
var points = chart.series[1].points;
for(var i = 0; i < points.length; i++) {
var point = points[i];
if(point.x === 1980) {
console.log(point.plotX); //263.51049191606
console.log(point.plotY); //40.81559999999999
}
}
UPDATE 1
Adding translated locations to an array:
var locations = [];
var points = chart.series[1].points;
for(var i = 0; i < points.length; i++) {
var point = points[i];
if(point.x === 1980 || point.x === 1990) {
locations.push({
x: point.plotX + chart.yAxis[0].left,
y: point.plotY + chart.yAxis[0].top
});
}
}
Updated fiddle
First time ever working with JS and HighCharts... But I'll try to formulate a question so it'll make sense!
At the moment I'm working with only 4 sources of data, which is incredibly easy to throw right in to highcharts.
The problem is, the 4 aggregated numbers is... well, not very consistent.
The numbers I have atm is: 349531093, 156777100, 572480, 7 and 0.
The first number and the second covers the whole funnel, which makes the plot very unattractive and hard to visually see the values.
(Yeah, yeah - the labels are brilliant, but I want to be able to visually see each section).
I've been reading through the documentation of the funnel plot, but I cannot find a way to limit the section size in any way.
So I tried to play around a bit with the different kind of limits, like:
minSize - The minimum size for a pie in response to auto margins. The pie will try to shrink to make room for data labels in side the
plot area, but only to this size. (which does exactly what it says,
so I'm not sure why I even tried it...)
size - that ofc just changed the size of the whole chart....
series: {
dataLabels: {
enabled: true,
format: '<b>{point.name}</b> ({point.y:,.0f})',
minSize: '10%',
color: 'black',
softConnector: true
},
neckWidth: '50%',
neckHeight: '50%',
minSize: '20%',
//-- Other available options
height: '200'
// width: pixels or percent
}
You can see my horrible attempt here at it here: JSFiddle thingy
So to the actual question: Is it possible to set an minimum limit for the section in the funnel?
Any suggestions or just a simple: "dude, not possible" is appreciated!
Cheers!
Unfortunately this is not supported (good idea to post this on userVoice!)
However I have created simple example that you can preprocess data and still display proper values: https://jsfiddle.net/69eey/2/
$(function () {
var dataEx = [
['Raw Events', 349531093],
['Filtered/Aggregated Events', 156777100],
['Correlated Events', 2792294],
['Use Case Events', 572480],
['Finalized', 0]
],
len = dataEx.length,
sum = 0,
minHeight = 0.05,
data = [],
i;
for(i = 0; i < len; i++){
sum += dataEx[i][1];
}
for(i = 0; i < len; i++){
var t = dataEx[i],
r = t[1] / sum;
data[i] = {
name: t[0],
y: ( r > minHeight ? t[1] : sum * minHeight ),
label: t[1]
}
}
It is only workaround of course. You also need to use formatter for a tooltip to make sure you will display proper values (like for dataLabels).
I took Paweł Fus's great example and extended it to include the tooltip correction. Just add the snippet below:
tooltip: {
formatter: function() {
return '<b>'+ this.key +
'</b> = <b>'+ Highcharts.numberFormat(this.point.label, 0) +'</b>';
}
},
JSFiddle with a working example:
HTML
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/funnel.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<div id="container" style="width: 600px; height: 400px; margin: 0 auto"></div>
JavaScript
$(function () {
var dataEx = [
['Raw Events', 349531093],
['Filtered/Aggregated Events', 156777100],
['Correlated Events', 2792294],
['Use Case Events', 572480],
['Finalized', 0]
],
len = dataEx.length,
sum = 0,
minHeight = 0.05,
data = [];
for(var i = 0; i < len; i++){
sum += dataEx[i][1];
}
for(var i = 0; i < len; i++){
var t = dataEx[i],
r = t[1] / sum;
data[i] = {
name: t[0],
y: ( r > minHeight ? t[1] : sum * minHeight ),
label: t[1]
}
}
$('#container').highcharts({
chart: {
type: 'funnel',
marginRight: 100
},
title: {
text: 'SEIM Metrics',
x: -50
},
tooltip: {
//enabled: false
formatter: function() {
return '<b>'+ this.key +
'</b> = <b>'+ Highcharts.numberFormat(this.point.label, 0) +'</b>';
}
},
plotOptions: {
series: {
dataLabels: {
enabled: true,
formatter: function(){
var point = this.point;
console.log(point);
return '<b>' + point.name + '</b> (' + Highcharts.numberFormat(point.label, 0) + ')';
},
minSize: '10%',
color: 'black',
softConnector: true
},
neckWidth: '50%',
neckHeight: '50%',
//-- Other available options
height: '200'
// width: pixels or percent
}
},
legend: {
enabled: false
},
series: [{
name: 'Unique users',
data: data
}]
});
});
You can try normalizing the values first by taking log.
log(349531093)=8.5
log(572480)=5.75
I have a Highcharts bar chart that I'm trying to add custom shapes to based on the bar values and position. To start with, I'm just trying to use highcharts.renderer.path, to add a line for each bar, as tall as the bar, positioned on the x axis based on a hard coded value. Here's a picture of what I mean:
This should be easy, and it is when the chart.type = "column". In the highcharts callback, I would use getBBox() on each bar, and translate() to convert the x axis value to a pixel value.
However, I've run into several problems when trying to do this with chart.type = "bar". First, all x and y values are switched (I assume this is how the author created the bar chart from a column chart in the first place). This is true for all the properties of the chart as well: plotLeft is now the top, plotTop is now the left.
This should work:
function (chart) {
$.each(chart.series[0].data, function (pointIndex, point) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
yStart = chart.plotTop+elem.x;
xStart = chart.plotLeft+elem.height;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yStart+point.pointWidth];
plotLine.attr = {
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
});
});
Full example: http://jsfiddle.net/Bh3J4/9/
The second issue may be a bug that can't be overcome. It appears that when there is more than one data point, all of the x and y values get mixed up between the points. Notice in the fiddle that the colors don't match the positions. I've created an issue on GitHub.
When there's just one point, it's not a problem. When there are two points, I could easily switch the values to get the right positioning. However when there are 3 or more points, I can't seem to figure out the logic for how the values get mixed up.
The third issue, is that the translate function doesn't seem to work on the xAxis for a bar chart, even though it does on the yAxis.
chart.yAxis[0].translate(4); // correct for bottom axis
chart.xAxis[0].translate(1); // incorrect for side axis
Is there another way to achieve what I'm looking for? Am I missing something in that Fiddle that's not actually a bug?
I was able to achieve the result I wanted, but I don't know if it's coincidental or a workaround for an actual bug. Regardless, it seems that using the x value from the reverse sorted array helped me line everything up correctly. Here's the callback function for highcharts:
function (chart) {
var benchmarks = { A: 1.5, B: 3.6, C: 2 },
reverseData = _.clone(chart.series[0].data).reverse();
_.each(chart.series[0].data, function (point, pointIndex) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
reverseElem = reverseData[pointIndex].graphic.element.getBBox(),
benchmark = benchmarks[point.category],
yStart = chart.plotTop+reverseElem.x,
xStart = chart.plotLeft+chart.yAxis[0].translate(benchmark),
yEnd = yStart+point.pointWidth-1;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yEnd];
plotLine.attr = {
'stroke-width': 1,
stroke: "red",
zIndex: 5
};
chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
var margin = 5,
xPadding = 10,
yPadding = 5,
xSplit = xPadding/2,
ySplit = yPadding/2,
text,
box;
text = chart.renderer.text("Top Perf Avg " + benchmark, xStart, yEnd+margin+16).attr({
color: "#646c79",
align: "center",
"font-family": "Arial, sans-serif",
"font-size": 9,
"font-weight": "bold",
style: "text-transform: uppercase",
zIndex: 7
}).add();
box = text.getBBox();
chart.renderer.path(["M", box.x-xSplit, box.y-ySplit,
"l", (box.width/2)+xSplit-margin, 0,
margin, -margin,
margin, margin,
(box.width/2)+xSplit-margin, 0,
0, box.height+yPadding,
-(box.width+xPadding), 0,
0, -(box.height+yPadding)])
.attr({
'stroke-width': 1,
stroke: "#cccccc",
fill: "#ffffff",
zIndex: 6
}).add();
});
}
See the complete working graph here: http://jsfiddle.net/Bh3J4/18/
In the fact, Highcharts rotate everything using transform, so use the same to rotate these lines, see example: http://jsfiddle.net/Bh3J4/19/
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = point.plotX - point.pointWidth / 2;
yStart = point.plotY;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(491,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}
Slight adjustment that seems to give precise alignment:
Pls note: changes to calc of xStart/yStart and change to transform translate parameter.
My approach was to make it work for column chart and then get translate refined.
The only unsatisfactory part is that xStart needs: xStart = elem.x+chart.plotLeft; in 'column' mode vs xStart = elem.x; in 'bar' mode...
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = elem.x;
yStart = chart.plotHeight - (elem.height/2) + chart.plotTop;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(542.5,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 5,
stroke: 'blue',
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}
Say i have the following code the event only seems to be firing every now and again take the two following examples.
First example this works great and the event fires everytime as expected
for (var i = 0; i < items; i++) {
var rect = new Kinetic.Rect({
x: 0,
y: 1+i,
width: 100,
height: 2,
fill: 'green'
});
rect.on('mouseover', function(evt) {
$('#console').text(evt.shape.index);
});
layer.add(rect);
}
view in action http://jsfiddle.net/6aTNn/5/
Here is my issue when adding a rotate value on the event doesn't seem to be firing correctly.
for (var i = 0; i < items; i++) {
var rect = new Kinetic.Rect({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
width: 100,
height: 1,
fill: 'green'
});
rect.on('mouseover', function(evt) {
console.log(evt.shape.index);
$('#console').text(evt.shape.index);
});
rect.rotate(i * angularSpeed / items);
// add the shape to the layer
layer.add(rect);
}
view in action http://jsfiddle.net/6aTNn/8/
Any help on this would be great been at this for hours and cant find a solution that works?
The nodes are too 'thin' and too overlapped for the event to fire properly.
Try increasing the height of each node and adding fewer of them.
For example, replace the i++ with i += 3 and increase the height to 2.
for (var i = 0; i < 800; i += 3) {
var rect = new Kinetic.Rect({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
width: 100,
height: 2,
fill: 'green'
});
// ...
}
I am new to Kineticjs and not a great javascript coder so I am hoping to get some help with this example.
http://jsfiddle.net/pwM8M/
I am trying to store the x axis on doors so when a redraw unrelated to the doors is done they don't go back to the default position. (multiple types of doors and windows too)
Each form element can have multiple quantities (more than one door) so I need a way to store/retrieve the data currently contained in the alert on jsfiddle.
I did some research but have come up empty. Can anyone help with what I have provided?
function OH(x,y,w,h) {
var oh = new Kinetic.Text({
x:x,
y: y,
width: w*scale,
height: h*scale,
stroke: wtc,
strokeWidth: 1,
fill: 'brown',
fontSize: 6,
fontFamily: 'Calibri',
padding:4,
text: w+' x '+h+' OH\n\n<- DRAG ->',
align: 'center',
textFill: 'white',
draggable: true,
dragConstraint:'horizontal',
dragBounds: {
left:xoffset, right: xoffset+width+length-(w*scale)
}
});
oh.on('dragend', function(e) {
alert(oh.getPosition().x);
});
window.south.add(oh);
}
Thanks,
I have fixed sized 40x40 rectangle here in which i am using dragging function. try this
var box = new Kinetic.Rect({
x: parseFloat(area.size.x),
y: parseFloat(area.size.y),
width: 40, //parseFloat(area.size.width)
height: 40,
fill: area.color,
stroke: 'black',
strokeWidth: 1,
opacity: 0.6,
id : area.id + id,
draggable: true,
dragBoundFunc: function(pos) {
// x
var newX = pos.x < 0 ? 40 : pos.x;
var newX = newX > _self.canvasWidth - area.size.width ? _self.canvasWidth - area.size.width : newX;
// y
var newY = pos.y < 0 ? 40 : pos.y;
var newY = newY > _self.canvasHeight - area.size.height ? _self.canvasHeight - area.size.height : newY;
return {
x: newX,
y: newY
};
Use this function
box.on('dragend', function() {
_self.draw = false;
_self.dragArea(area, box);
});
and try this to play with x y coordinates
dragArea: function(area, box){
if(box){
$$('#' + this.formId + ' [name="areas[' + area.id + '][size][x]"]').first().value = parseInt(box.attrs.x);
$$('#' + this.formId + ' [name="areas[' + area.id + '][size][y]"]').first().value = parseInt(box.attrs.y);
area.size.x = parseInt(box.attrs.x);
area.size.y = parseInt(box.attrs.y);
}
},
Create a new array before the function, like so:
var xArray = new Array();
then inside your function
oh.on('dragend', function(e) {
alert(oh.getPosition().x);
// ADD NEW ITEM TO ARRAY, STORE X POSITION
xArray.push(oh.getPosition().x);
});
and so all the x values get stored in the array.
If you need to clear the array, you can simply create a new one again with the same name.
And you can iterate through it with a loop if needed.
updated:
http://jsfiddle.net/pwM8M/2/