I am using the highcharts export server node module to export treemap charts. I'm trying to create a custom layoutAlgorithm. I already have this working on the client side, but when I move the code to the server I get errors. Here's some example code:
import * as exporter from 'highcharts-export-server';
import Highcharts from 'highcharts';
function quadrants(parent, children) {
let width = parent.width / 2;
let height = parent.height / 2;
let areas = [
{
x: parent.x + width,
y: parent.y,
width,
height,
},
{
x: parent.x,
y: parent.y,
width,
height,
},
{
x: parent.x,
y: parent.y + height,
width,
height,
},
{
x: parent.x + width,
y: parent.y + height,
width,
height,
},
];
for (let i = 0; i < children.length; i++) {
areas.push();
}
return areas;
}
// This is the problem line
Highcharts.seriesTypes.treemap.prototype.quadrants = quadrants;
const exportSettings = {
// ...
series: [
type: 'treemap',
levels: [{
level: 1,
layoutAlgorithm: 'quadrants',
}],
],
// ...
}
return new Promise((resolve, reject) => {
exporter.export(exportSettings, (err, res) => {
if (err) reject(err);
resolve(res);
});
});
When I run this code I get the following error
TypeError: Cannot read property 'treemap' of undefined
Is this the incorrect way to set the layout algorithm or is there another way to set a layout algorithm with the highcharts export server?
Thanks
Related
I am trying to use Font Awesome icons as markers in HighCharts line chart. With help from fellow developers on Stack Overflow, I have managed to do that. One problem left is that now, whenever I hover over the markers and then leave hovering, they just move up a bit from the line and stay there forever. I really have no idea why.
This is the fiddle: https://jsfiddle.net/vf0g4u5k/12/. Appreciate any help.
The plug-in to use Font Awesome with HighCharts
(function (H) {
function symbolWrap(proceed, symbol, x, y, w, h, options) {
if (symbol.indexOf('text:') === 0) {
var text = symbol.split(':')[1],
svgElem = this.text(text, x, y + h)
.css({
fontFamily: '"Font Awesome 5 Free"',
fontSize: (h * 2 ) + "px"
});
if (svgElem.renderer.isVML) {
svgElem.fillSetter = function (value, key, element) {
element.style.color = H.Color(value).get('rgb');
};
}
return svgElem;
}
if (symbol.indexOf('textn:') === 0) {
var text = symbol.split(':')[1],
svgElem = this.text(text, x, y + h)
.css({
fontFamily: '"Font Awesome 5 Free"',
fontSize: (h * 2 ) + "px",
fontWeight: 900
});
if (svgElem.renderer.isVML) {
svgElem.fillSetter = function (value, key, element) {
element.style.color = H.Color(value).get('rgb');
};
}
return svgElem;
}
return proceed.apply(this, [].slice.call(arguments, 1));
}
H.wrap(H.SVGRenderer.prototype, 'symbol', symbolWrap);
if (H.VMLRenderer) {
H.wrap(H.VMLRenderer.prototype, 'symbol', symbolWrap);
}
// Load the font for SVG files also
H.wrap(H.Chart.prototype, 'getSVG', function (proceed) {
var svg = proceed.call(this);
svg = '<?xml-stylesheet type="text/css" ' +
'href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" ?>' +
svg;
return svg;
});
}(Highcharts));
I ended up doing like this. Posting in case it may help someone else.
point: {
events: {
mouseOut: function() {
var index = this.index
for( var series of this.series.chart.series){
for(var j=0;j<series.points.length;j++){
if(j === index && series.points[j].graphic){series.points[j].graphic.attr({'translateY': 8})}
}
};
}
}
}
You can adjust your custom marker settings by setting translateX and translateY like this
var text = symbol.split(':')[1],
svgElem = this.text(text, x, y)
.attr({
translateY: h,
translateX: -1
})
.css({
fontFamily: '"Font Awesome 5 Free"',
fontSize: (h * 2 ) + "px"
});
I am trying to draw multiple rectangles in time loop every 250ms using Konva.js, but the canvas updates only after loop is finish. What am I doing wrong?
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
var layer = new Konva.Layer();
stage.add(layer);
for (let i=0; i < 10; i++){
var rect1 = new Konva.Rect({
x: i*25,
y: 20,
width: 20,
height: 20,
fill: 'red'
});
layer.add(rect1);
layer.draw();
sleep(250);
}
Your sleep method blocks main JS thread. You need to use async methods to wait. There are many ways to do that. With modern js you can do this:
// noprotect
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const delay = (time) => new Promise(resolve => setTimeout(resolve, time));
async function startAddingShapes() {
for (let i=0; i < 10; i++){
const rect = new Konva.Rect({
x: i*25,
y: 20,
width: 20,
height: 20,
fill: 'red'
});
layer.add(rect);
await delay(250);
}
}
startAddingShapes();
Demo: https://jsbin.com/sahamulako/1/edit?html,js,output
I am using horizontal bar chart with continuous update of series data. This is achieved successfully, but now i want these series to be sorted on data (continuously with every update of series) in desc order with animation. I mean when a bar get max value then move it to the top with animation.
how can i achieve this?
That type of functionality is not supported by default in Highcharts. Below you can find an example that shows how you can achieve the wanted result by custom code:
var options = {
chart: {
type: 'bar'
},
xAxis: {
categories: ['Cat1', 'Cat2', 'Cat3'],
},
series: [{
data: [1000, 900, 800]
}]
};
var chart = Highcharts.chart('container', options);
// Add custom data labels
chart.series[0].points.forEach(function(point, i) {
var x = chart.plotWidth - point.plotY + chart.plotLeft,
y = chart.xAxis[0].ticks[i].label.xy.y;
point.customDataLabel = chart.renderer.text(
point.y,
x,
y
)
.css({
color: '#000000',
fontSize: '14px'
})
.attr({
zIndex: 3
})
.add();
setAlign(point.customDataLabel);
});
function setAlign(label, xPos) {
var align = 'left',
bbox = label.getBBox();
if (chart.chartWidth < (xPos ? xPos : bbox.x + bbox.width) + 50) {
align = 'right';
}
label.attr({
align: align
})
}
var update = function() {
var points = chart.series[0].points;
chart.series[0].setData([Math.round(Math.random() * 1000), Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)]);
};
var rotate = function() {
var points = chart.series[0].points,
labelX,
ticks = chart.xAxis[0].ticks;
var sortedPoints = points.slice();
sortedPoints.sort(function(a, b) {
return b.y - a.y;
});
points.forEach(function(point, i) {
sortedPoints.forEach(function(sPoint, j) {
if (point === sPoint) {
labelX = chart.plotWidth - points[i].plotY + chart.plotLeft;
// Animate the column
points[i].graphic.animate({
x: points[j].shapeArgs.x
});
// Animate the label
points[i].customDataLabel.attr({
text: points[i].y
}).animate({
y: ticks[j].label.xy.y,
x: labelX
});
setAlign(points[i].customDataLabel, labelX);
// Animate the axis label
ticks[i].label.animate({
y: ticks[j].label.xy.y
});
}
});
});
};
document.getElementById("button").addEventListener("click", function() {
update();
rotate();
}, false);
Live demo: https://jsfiddle.net/BlackLabel/mg5bv3s8/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGElement#animate
Is it possible to omit the vertical lines from the "square wave" line? I think you could call that a level line, here's an illustration:
The easiest way to achieve this look is to use a scatter chart with a custom "line" symbol:
// define a custom line symbol
Highcharts.SVGRenderer.prototype.symbols.line = function (x, y, w, h) {
return ['M', x, y, 'L', x - w * 2, y, 'M', x, y, 'L', x + w * 2, y, 'z'];
};
if (Highcharts.VMLRenderer) {
Highcharts.VMLRenderer.prototype.symbols.cross = Highcharts.SVGRenderer.prototype.symbols.cross;
}
$('#container').highcharts({
chart: {
type: 'scatter'
},
title: {
text: 'Look At Lines!!'
},
series: [{
name: 'Line Symbol',
data: [54.4, 29.9, {y: 129.2, radius: 8}, 144.0, 176.0, 135.6],
marker: {
symbol: 'line',
lineColor: null,
lineWidth: 2
}
}]
});
Note that you can adjust the length of an individual point by upping the radius.
Fiddle here.
Produces:
While above answer works fine, the issue I faced was that width of the line symbol is not the same as column width when displayed in combination with a column chart.
example image
Another option is to extend column type chart in a new chart type like so:
$(function () {
(function (H) {
var each = H.each,
pick = H.pick,
Series = H.Series,
seriesType = H.seriesType;
seriesType('floatingStep', 'column', {
fixedPointLength: 2
}, /** #lends seriesTypes.floatingStep.prototype */ {
/**
* Translate each point to the plot area coordinate system and find shape positions
*/
translate: function () {
var series = this,
chart = series.chart,
options = series.options,
dense = series.dense = series.closestPointRange * series.xAxis.transA < 2,
borderWidth = series.borderWidth = pick(
options.borderWidth,
dense ? 0 : 1 // #3635
),
yAxis = series.yAxis,
threshold = options.threshold,
translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
minPointLength = pick(options.minPointLength, 5),
fixedPointLength = options.fixedPointLength,
metrics = series.getColumnMetrics(),
pointWidth = metrics.width,
seriesBarW = series.barW = Math.max(pointWidth, 1 + 2 * borderWidth), // postprocessed for border width
pointXOffset = series.pointXOffset = metrics.offset;
if (chart.inverted) {
translatedThreshold -= 0.5; // #3355
}
// When the pointPadding is 0, we want the columns to be packed tightly, so we allow individual
// columns to have individual sizes. When pointPadding is greater, we strive for equal-width
// columns (#2694).
if (options.pointPadding) {
seriesBarW = Math.ceil(seriesBarW);
}
Series.prototype.translate.apply(series);
// Record the new values
each(series.points, function (point) {
var yBottom = pick(point.yBottom, translatedThreshold),
safeDistance = 999 + Math.abs(yBottom),
plotY = Math.min(Math.max(-safeDistance, point.plotY), yAxis.len + safeDistance), // Don't draw too far outside plot area (#1303, #2241, #4264)
barX = point.plotX + pointXOffset,
barW = seriesBarW,
barY = Math.min(plotY, yBottom),
up,
barH = Math.max(plotY, yBottom) - barY;
// Handle options.minPointLength
if (Math.abs(barH) < minPointLength) {
if (minPointLength) {
barH = minPointLength;
up = (!yAxis.reversed && !point.negative) || (yAxis.reversed && point.negative);
barY = Math.abs(barY - translatedThreshold) > minPointLength ? // stacked
yBottom - minPointLength : // keep position
translatedThreshold - (up ? minPointLength : 0); // #1485, #4051
}
}
if (fixedPointLength) {
barH = fixedPointLength;
}
// Cache for access in polar
point.barX = barX;
point.pointWidth = pointWidth;
// Fix the tooltip on center of grouped columns (#1216, #424, #3648)
point.tooltipPos = chart.inverted ? [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - barX - barW / 2, barH] : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
// Register shape type and arguments to be used in drawPoints
point.shapeType = 'rect';
point.shapeArgs = series.crispCol.apply(
series,
point.isNull ?
// #3169, drilldown from null must have a position to work from
// #6585, dataLabel should be placed on xAxis, not floating in the middle of the chart
[barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]
);
});
},
});
})(Highcharts);
});
View in JS Fiddle
I was wondering if someone could help me find the solution to this.
I've made a very simple animation using KineticJS.
All works perfect on desktop, unfortunately not on mobile devices (iPhone, iPad, Android).
Result is a slowish performance but most importantly distorted shapes.
I suspect it has something to do with resolution or viewport but am not sure.
Preview is on www.bartvanhelsdingen.com
Any suggestions are highly appreciated.
Below is the code:
var shapes = {
sizes: [30, 40, 50, 55, 60, 80],
gradients: [
[0, '#fdfaee', 1, '#524f43'],
[0, '#a39175', 1, '#dbae5e'],
[0, '#b4c188', 1, '#f3de7c'],
[0, '#eaf2ef', 1, '#587c71'],
[0, '#a39175', 1, '#dbae5e'],
[0, '#61845c', 1, '#b4b092']
],
},
dims = {
width: 300,
height: 500
},
stage = new Kinetic.Stage({
container: 'animation',
width: dims.width,
height: dims.height,
x: 0,
y: 0,
draggable: false
});
function getRandomColor() {
return colors[getRandomFromInterval(0, colors.length - 1)];
}
function getRandomGradient() {
return gradients[getRandomFromInterval(0, gradients.length - 1)];
}
function getRandomFromInterval(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function getRandomSpeed() {
var speed = getRandomFromInterval(1, 1);
return getRandomFromInterval(0, 1) ? speed : speed * -1;
}
function createGroup(x, y, size, strokeWidth) {
return new Kinetic.Group({
x: x,
y: y,
width: size,
height: size,
opacity: 0,
draggable: false,
clipFunc: function (canvas) {
var context = canvas.getContext();
context.beginPath();
context.moveTo(0, 0);
context.lineTo(0, size);
context.lineTo(size, size);
context.lineTo(size, 0);
context.rect(strokeWidth, strokeWidth, size - strokeWidth * 2, size - strokeWidth * 2);
}
});
}
function createShape(size, gradient, strokeWidth, cornerRadius) {
return new Kinetic.Rect({
x: 0,
y: 0,
width: size,
height: size,
fillLinearGradientStartPoint: [size, 0],
fillLinearGradientEndPoint: [size, size],
fillLinearGradientColorStops: gradient,
opacity: 1,
lineJoin: 'bevel',
strokeWidth: 0,
cornerRadius: cornerRadius
});
}
var layer = new Kinetic.Layer(),
animAttribs = [];
for (var n = 0; n < 6; n++) {
var size = shapes.sizes[n],
strokeWidth = Math.ceil(size * 0.12),
cornerRadius = Math.ceil(size * 0.04),
gradient = shapes.gradients[n],
x = getRandomFromInterval(size, dims.width) - size,
y = getRandomFromInterval(size, dims.height) - size;
var group = createGroup(x, y, size, strokeWidth);
var shape = createShape(size, gradient, strokeWidth, cornerRadius);
animAttribs.push({
nextChange: getRandomFromInterval(1, 3) * 1000,
startTime: 1000,
duration: 0,
x: getRandomSpeed(),
y: getRandomSpeed()
});
group.add(shape);
layer.add(group);
}
stage.add(layer);
anim = new Kinetic.Animation(function (frame) {
var time = frame.time,
timeDiff = frame.timeDiff,
frameRate = frame.frameRate;
for (var n = 0; n < layer.getChildren().length; n++) {
var shape = layer.getChildren()[n],
opacity = shape.getOpacity() + 0.01 > 1 ? 1 : shape.getOpacity() + 0.01,
attribs = animAttribs[n],
x, y;
if (attribs.duration >= attribs.nextChange) {
attribs.x = getRandomSpeed();
attribs.y = getRandomSpeed();
attribs.nextChange = getRandomFromInterval(3, 5) * 1000;
attribs.duration = 0;
}
if (time >= attribs.startTime) {
if (shape.getX() + attribs.x + shape.getWidth() >= stage.getWidth() || shape.getX() + attribs.x - shape.getWidth() / 2 <= 0) {
attribs.x *= -1;
}
if (shape.getY() + attribs.y + shape.getHeight() >= stage.getHeight() || shape.getY() + attribs.y - shape.getHeight() / 2 <= 0) {
attribs.y *= -1;
}
x = shape.getX() + attribs.x;
y = shape.getY() + attribs.y;
attribs.duration += timeDiff;
shape.setOpacity(opacity);
shape.setX(x);
shape.setY(y);
}
}
}, layer);
anim.start();
the problem you are facing is, that clipFunc isn't currently working on devices with pixelratio != 1.
This problem came up in this post as well. Eric Rowell, the creator of KineticJS added this issue to his release scedule for late September.
So there is nothing wrong with your animations, they're working as expected, but you can't see them because of the distorted clipping region
To resolve this issue "unofficially" you can simply replace the last line of the _clip function in your kinetic.js with the following: context.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); (credits for that go to Mark Smits)