There is an example for highcharts:
http://jsfiddle.net/highcharts/z9zXM/
However I couldn't reverse x axis and y axis at table. I mean I want it like:
Tokyo Jan Feb ..
New York
Berlin
London
Also I want to locate that table at middle under chart.
Any ideas?
This is how the loops should be:
// draw category labels
$.each(series, function(serie_index, serie) {
renderer.text(
serie.name,
cellLeft + cellPadding,
tableTop + (serie_index + 2) * rowHeight - cellPadding
)
.css({
fontWeight: 'bold'
})
.add();
});
$.each(chart.xAxis[0].categories, function(category_index, category) {
cellLeft += colWidth;
// Apply the cell text
renderer.text(
category,
cellLeft - cellPadding + colWidth,
tableTop + rowHeight - cellPadding
)
.attr({
align: 'right'
})
.css({
fontWeight: 'bold'
})
.add();
$.each(series, function(i) {
renderer.text(
Highcharts.numberFormat(series[i].data[category_index].y, valueDecimals) + valueSuffix,
cellLeft + colWidth - cellPadding,
tableTop + (i + 2) * rowHeight - cellPadding
)
.attr({
align: 'right'
})
.add();
});
});
Here is the link: http://jsfiddle.net/pJ3qL/1/
Then you should draw the table borders inside the loops again if you want ;)
Related
1. Background:
I use konvajs to create a "table" component:
structure:
Stage
|
Layer
|
+-----+--------------+
| |
Group(tableGroup) Group(tableGroup)
|
+--------------+--------+
| |
Group(cellGroup) Group(cellGroup)
|
+----+----+
| |
Shape(Rect) Shape(Text)
image:
table component
2. Target:
I want every text shape to be width adaptive.
I found it in the official documents of konvajs:
How to change width of the text with transforming tool? https://konvajs.org/docs/select_and_transform/Resize_Text.html
Scenario 1: add all "cellGroup" to "transformer"
// My code
tableGroup.on('click', function (e) { // Click the selected table
tr.nodes([])
tableGroup.getChildren().map(function(item) {
tr.nodes(tr.nodes().concat(item)); // Add all cellGroups to transformer
item.off('transform')
item.on('transform', (e) => {
item.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
item.getChildren().map(function(child){
child.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
})
})
})
});
Scheme 2: add "tablegroup" to "transformer"
// My code
tableGroup.on('click', function (e) { // Click the selected table
tr.nodes([tableGroup]) // Add all tableGroup to transformer
tableGroup.on('transform', (e) => {
tableGroup.getChildren().map(function(item) {
item.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
item.getChildren().map(function(child){
child.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
})
})
})
});
conclusion: Scheme 1 is feasible, but scheme 2 is not.My requirement is to add "tableGroup" to transformer and realize text width adaptation.Find a solution, thank you very much.
3. Other:
Q: Why must "tableGroup" be added to "transformer"?
A: Because when moving a "Group" or "Shape" with "Transformer", the coordinates (x, y) of the "Group" or "Shape" will be changed. I don't want to change the coordinates of "cellGroup", I want to change the coordinates of "tableGroup" (x, y). Or you have a better solution.Find a solution, thank you very much.
Here is a solution using two groups - one for the table outline and a second to contain the cells. The cell group has its attrs set to follow the table group as it is transformed - excluding the scale!
This is not a perfect solution that you can cut & paste as a usable component but should show you a potential alternative.
/*
* From here onwards we set up the stage and its contents.
*/
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
layer = new Konva.Layer(),
tblGroup = new Konva.Group({draggable: true}),
cellGroup = new Konva.Group(),
cellRect = new Konva.Rect({strokeWidth: 1, stroke: 'black', name: 'cellRect'}),
cellText = new Konva.Text({fontName: "Arial", fontSize: 20, fill: 'black', name: 'cellText'}),
tr = new Konva.Transformer();
stage.add(layer);
layer.add(tr);
// Using this plain JS objet to define the table and relative cell positions.
const tblData = {
position: { x: 100, y: 100, width: 202, height: 82},
cells: [
{x: 1, y: 1, width: 100, height: 40, text: 'Cell 1-1'},
{x: 101, y: 1, width: 100, height: 40, text: 'Cell 1-2'},
{x: 1, y: 41, width: 100, height: 40, text: 'Cell 2-1'},
{x: 101, y: 41, width: 100, height: 40, text: 'Cell 2-2'},
]
}
const tableGroup = tblGroup.clone({x: tblData.position.x, y: tblData.position.y}),
tblPosGroup = tableGroup.clone(), // position exactly as tableGroup.
tblRect = cellRect.clone({x:0, y: 0, width: tblData.position.width, height: tblData.position.height});
tblRect.stroke('red');
tableGroup.add(tblRect);
// add the cells.
for (let i = 0; i < tblData.cells.length; i++){
const
cell = cellGroup.clone({}),
rect = cellRect.clone(tblData.cells[i]),
text = cellText.clone(tblData.cells[i]);
// Note we stach the positioning data into the Konva shape instances in a custom attr - used in the transform event
rect.setAttrs({posData: tblData.cells[i]});
text.setAttrs({posData: tblData.cells[i]});
cell.add(rect,text);
tblPosGroup.add(cell);
}
layer.add(tableGroup, tblPosGroup);
tableGroup.on('transform', (e) => {
// make the cells group follow the tbl group as it is transformed
tblPosGroup.setAttrs({position: tableGroup.position(), rotation: tableGroup.rotation()});
// find all the objects we want to manager - using the shape name() attr which we search with a dot prefix.
tblPosGroup.find('.cellRect, .cellText').map(function(item) {
const cellPos = item.getAttr("posData");
// set the position and size of the cells referring to original position & size data and applying scale from transformer.
item.setAttrs({
x: cellPos.x * tableGroup.scaleX(),
y: cellPos.y * tableGroup.scaleY(),
width: cellPos.width * tableGroup.scaleX(),
height: cellPos.height * tableGroup.scaleY()
});
})
})
tblPosGroup.on('click', function (e) { // When the table is clicked - actually we listen on the cells group as this will get the click
tr.nodes([tableGroup]); // Add all tableGroup to transformer
e.cancelBubble = true;
})
stage.on('click', function (e) {
tr.nodes([]);
})
body {
margin: 20px;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<p>A table-like construction. The aim was to keep cell contents unchanged in size and scale whilst allowing a transformer to be used.</p>
<p>Solution is two groups. Group#1 is the main table rect and group#2 is the cells. Group#2 attrs are set to follow group#1 attrs in the group#1.onTransform event listener.</p>
<div id="container"></div>
I'm working on a Highchart plot using a navigator and I've noticed that my outline color is applied to the whole navigator's top, not just the selected data range.
To visualize:
I'm talking about the purple line going through the whole chart, while I'd like it to be just a part of the selection box, the rest of the top outline should have different color/width. How can I achieve that?
Here's a snippet for reference, found it online and it's rather simple but it shows exactly what's happening:
Highcharts.stockChart('container', {
navigator: {
outlineColor: 'blue',
outlineWidth: 2,
series: {
lineWidth: 3,
fillOpacity: 0.3
}
},
rangeSelector: {
selected: 1
},
series: [{
name: 'USD to EUR',
data: usdeur
}]
});
<div id="container" style="height: 400px; min-width: 600px"></div>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
<script type="text/javascript" src="https://www.highcharts.com/samples/data/usdeur.js"></script>
All the blue lines are created by one svg path element. The only way to change a navigator outline is to wrap the function responsible for drawing it and change its path.
For example:
(function(H) {
H.wrap(H.Navigator.prototype, 'drawOutline', function(procced, zoomedMin, zoomedMax, inverted, verb) {
var navigator = this,
maskInside = navigator.navigatorOptions.maskInside,
outlineWidth = navigator.outline.strokeWidth(),
halfOutline = outlineWidth / 2,
outlineCorrection = (outlineWidth % 2) / 2, // #5800
outlineHeight = navigator.outlineHeight,
scrollbarHeight = navigator.scrollbarHeight,
navigatorSize = navigator.size,
left = navigator.left - scrollbarHeight,
navigatorTop = navigator.top,
verticalMin,
path;
if (inverted) {
left -= halfOutline;
verticalMin = navigatorTop + zoomedMax + outlineCorrection;
zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
path = [
'M',
left + outlineHeight,
navigatorTop - scrollbarHeight - outlineCorrection, // top edge
'L',
left + outlineHeight,
verticalMin, // top right of zoomed range
'L',
left,
verticalMin, // top left of z.r.
'L',
left,
zoomedMax, // bottom left of z.r.
'L',
left + outlineHeight,
zoomedMax, // bottom right of z.r.
'L',
left + outlineHeight,
navigatorTop + navigatorSize + scrollbarHeight // bottom edge
].concat(maskInside ? [
'M',
left + outlineHeight,
verticalMin - halfOutline, // upper left of zoomed range
'L',
left + outlineHeight,
zoomedMax + halfOutline // upper right of z.r.
] : []);
} else {
zoomedMin += left + scrollbarHeight - outlineCorrection;
zoomedMax += left + scrollbarHeight - outlineCorrection;
navigatorTop += halfOutline;
path = [
'M',
zoomedMin,
navigatorTop, // upper left of zoomed range
'L',
zoomedMin,
navigatorTop + outlineHeight, // lower left of z.r.
'L',
zoomedMax,
navigatorTop + outlineHeight, // lower right of z.r.
'L',
zoomedMax,
navigatorTop, // upper right of z.r.
].concat(maskInside ? [
'M',
zoomedMin - halfOutline,
navigatorTop, // upper left of zoomed range
'L',
zoomedMax + halfOutline,
navigatorTop // upper right of z.r.
] : []);
}
navigator.outline[verb]({
d: path
});
});
}(Highcharts));
Live example: https://jsfiddle.net/BlackLabel/Lrgok19a/
Useful thread: https://www.highcharts.com/forum/viewtopic.php?t=41155
I have created a column chart using Highchart, and would like to add additional text and value to the tooltip, without using the point.y or point.x and i am not trying to work out the sum/avg, its a self-generated value and text.
Any suggestion please.Please see highlighted example
Declare tooltip options with formatter function:
options: {
...,
tooltip: {
formatter: function () {
return 'The value for <b>' + this.x +
'</b> is <b>' + this.y + '</b>'
+ '<br/>' + 'Extra data: <b>' + this.point.extra + '</b>';
},
}
}
And define series data like below:
data:[
{
y: 49.9,
extra: 49.9
},
{
y: 71.5,
extra: 71.5
},
...
],
See jsfiddle code here
I am attempting to create a donut chart with (long) labels. The container for the chart is small (and dynamic). I keep running into a problem where the labels for the outer chart are cutoff.
series: [{
name: 'Browsers',
data: browserData,
size: '65%',
dataLabels: {
formatter: function() {
return this.y > 5 ? this.point.name : null;
},
color: 'white',
distance: -30
}
}, {
name: 'Versions',
data: versionsData,
size: '85%',
innerSize: '65%',
dataLabels: {
formatter: function() {
// display only if larger than 1
return this.y > 1 ? '<b>'+ this.point.name +':</b> '+ this.y +'%' : null;
}
}
}]
jsfiddle: http://jsfiddle.net/sw99B/
What I am trying to accomplish is the auto pie size, as is the case with simple pies:
series: [{
name: 'Versions',
data: versionsData,
dataLabels: {
formatter: function() {
// display only if larger than 1
return this.y > 1 ? '<b>'+ this.point.name +':</b> '+ this.y +'%' : null;
}
}
}]
jsfiddle: http://jsfiddle.net/4P4D5/
The problem with donuts is that I have to specify the size of the inner & outer donut. This causes the two pies making the donut to have fixed radius. Ideally, I would have the inner pie be a percentage of the outer; and let the outer pie have an auto-size.
Any suggestions on how to accomplish this?
The dataLabels: formatter... is only controlling whether or not to show datalabel for thinner slices. It's not controlling the size of the pie chart. That is being specified by the series:[{size parameter. In your example it's set to 85% of the container which does lead to overflow.
According to the docs
The default behaviour (as of 3.0) is to scale to the plot area and
give room for data labels within the plot area.
If you comment out the size on your example, it does indeed squeeze in the labels but I'm not sure you'll love the look of it.
The following minor patch to highcharts.src.js v4.0.1 achieves the desired behaviour:
--- highcharts.src.orig.js 2014-04-24 08:25:52.000000000 +0000
+++ highcharts.src.js 2014-06-24 13:57:42.957605307 +0000
## -12167,6 +12167,22 ##
positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
smallestSize = mathMin(plotWidth, plotHeight),
isPercent;
+
+ /**
+ * Allow a chart (pie) to specify a size relative to another series. In
+ * that case, simply copy the center position of the parent, and scale
+ * the radius.
+ */
+ if ( options.relativeSize ) {
+ parentPositions = chart.series[options.relativeSize.parentSeries].center;
+ positions[0] = parentPositions[0];
+ positions[1] = parentPositions[1];
+ positions[2] = options.relativeSize.size * parentPositions[2];
+
+ return map(positions, function (length, i) {
+ return positions[i]
+ });
+ }
return map(positions, function (length, i) {
isPercent = /%$/.test(length);
Then, when configuring the donut:
series: [{
name: 'Browsers',
data: browserData,
center: ['50%', '50%'],
dataLabels: {
formatter: function() {
return this.y > 5 ? this.point.name : null;
},
color: 'white',
distance: -30
}
}, {
name: 'Versions',
data: versionsData,
relativeSize: {
parentSeries: 0, // index of parent series
size: 0.75 // 75% of parent pie
}
dataLabels: {
formatter: function() {
// display only if larger than 1
return this.y > 1 ? ''+ this.point.name +': '+ this.y +'%' : null;
}
}
}]
Note that the parent (outer) pie must be explicitly centered in the container, otherwise in certain cases the two pies may not be concentric.
Also, requiring the index of the series to be specified is clearly not ideal, but in a tightly controlled graph (as is a donut), it seems to do the job.
Unfortunately this option is not available, so I advice you to post your request on the uservoice
I'd like to have the y-axis data labels return 'N/A' for null values. Currently nothing is displayed. I tried using the y-formatter function to say if the y value equaled null, return N/A but haven't had luck for some reason? I'm somewhat new to Highcharts, so please forgive the basic nature of the question.
formatter: function() {
if (this.y !== null)
return 'test';
var chart = this.series.chart;
chart.renderer.text('n/a', this.point.plotX - 10, chart.plotHeight - 10).add(this.series.group)
},
Here's the link to the JSFiddle: http://jsfiddle.net/v5vJR/
Remove format from your dataLabels options. Then you need to properly calculate where put that label. plotY will be undefined, since there is no value, right?
formatter: function () {
var str;
if (this.y !== null) {
return this.y + '%';
} else {
var chart = this.series.chart,
categoryWidth = chart.plotHeight / chart.xAxis[0].categories.length,
offset = (this.point.x + 1) * categoryWidth - this.point.pointWidth + 3; //
chart.renderer.text('n/a', chart.plotLeft, chart.plotTop + offset).add();
}
return false;
},
Demo: http://jsfiddle.net/v5vJR/3/
Solution
Documentation: http://api.highcharts.com/highcharts#Renderer
Fiddle: http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/highcharts/members/renderer-text/
chart.renderer.text('<span style="color: #a2a5a1">N/A</span>', chart.plotLeft, chart.plotTop + offset)
.css({
color: '#a2a5a1',
fontSize: '11px'
})
.add();