Highcharts / Highstock step line without vertical "transition" lines? - highcharts

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

Related

Is there a image pattern stroke option for lines?

I've been trying to look over the Konva shape library and haven't found a stroke reapeating pattern method. I've been trying to look for a way to implement https://stackoverflow.com/a/32323610/20557085 into the shape's sceneFunc, but ended up with a static version that keeps itself in the top right corner of the canvas at all times, even if the canvas/camera is moved/dragged.
The end-goal would be to have a image that repeats itself following a line's bezier curve of points, that I can change the width of.
The question would be if there is something I am missing that is already a part of Konva, or if I should continue to trial my way through the sceneFunc?
The class component used in my attempt, that ended up static:
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
import { Stage, Layer, Image, Shape } from 'react-konva';
var PI = Math.PI;
class URLImageStroke extends React.Component {
constructor(props) {
super(props)
this.state = {
image: null,
points: [{ x: 0, y: 0 }, { x: 100, y: 100 }, { x: 150, y: 50 }, { x: 200, y: 200 }]
};
}
componentDidMount() {
this.loadImage();
this.getPoints()
}
loadImage() {
// save to "this" to remove "load" handler on unmount
this.image = new window.Image();
this.image.src = this.props.src;
this.image.addEventListener('progress', (e) => console.log(e))
this.image.addEventListener('load', this.handleLoad);
}
handleLoad = () => {
this.setState({
image: this.image,
});
};
getPoints = () => {
let points = [];
//for (let i = 0; this.state.points.length > i; i++) {
const s = this.state.points[0];
const c1 = this.state.points[1];
const c2 = this.state.points[2];
const e = this.state.points[3];
for (var t = 0; t <= 100; t += 0.25) {
var T = t / 100;
// plot a point on the curve
var pos = getCubicBezierXYatT(s, c1, c2, e, T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x, c1.x, c2.x, e.x, T);
var ty = bezierTangent(s.y, c1.y, c2.y, e.y, T);
var a = Math.atan2(ty, tx) - PI / 2;
// save the x/y position of the point and the tangent angle
// in the points array
points.push({
x: pos.x,
y: pos.y,
angle: a
});
}
this.setState({
points: points
});
}
render() {
return (
<Shape
x={50}
y={50}
width={this.props?.width}
height={this.props?.height}
image={this.state.image}
points={this.state?.points}
sceneFunc={(ctx, shape) => {
const img = shape.attrs.image;
if (!img) {
console.log("no image")
return;
}
const points = shape.attrs.points;
if (!points) {
console.log("no points")
return;
}
// Note: increase the lineWidth if
// the gradient has noticable gaps
ctx.lineWidth = 8;
ctx.strokeStyle = 'skyblue';
let sliceCount = 0;
// draw a gradient-stroked line tangent to each point on the curve
for (let i = 0; i < points.length; i++) {
let p = points[i];
ctx.translate(p.x, p.y);
ctx.rotate(p.angle - PI / 2);
// draw multiple times to fill gaps on outside of rope slices
ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
++sliceCount;
if (sliceCount > (img.width - 1)) { sliceCount = 0; }
}
//ctx.strokeShape(this);
}
}
/>
);
}
}
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt, controlPt1, controlPt2, endPt, T) {
var x = CubicN(T, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
var y = CubicN(T, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
return ({ x: x, y: y });
}
// cubic helper formula at T distance
function CubicN(T, a, b, c, d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
export default URLImageStroke;

Highcharts export server layout algorithm error

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

Highcharts: How we can sort series with animation?

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 possible to have a Title (on axis) that rotates when draggable box (3d) rotate?

I'm trying to change some things in the graphic "3D scatter chart Draggable Box"
http://www.highcharts.com/demo/3d-scatter-draggable/grid-light
Is possible to add a title to an axis, but that it rotates when I turn the box?
Now, if i add a title in this way:
yAxis: {
title: {
text: "Latitude"
}
},
When i rotate the box, the title doesn't follow the yAxis....
This is the code to rotate the chart:
$(chart.container).bind('mousedown.hc touchstart.hc', function (e) {
e = chart.pointer.normalize(e);
var posX = e.pageX,
posY = e.pageY,
alpha = chart.options.chart.options3d.alpha,
beta = chart.options.chart.options3d.beta,
newAlpha,
newBeta,
sensitivity = 5; // lower is more sensitive
$(document).bind({
'mousemove.hc touchdrag.hc': function (e) {
// Run beta
newBeta = beta + (posX - e.pageX) / sensitivity;
newBeta = Math.min(100, Math.max(-100, newBeta));
chart.options.chart.options3d.beta = newBeta;
// Run alpha
newAlpha = alpha + (e.pageY - posY) / sensitivity;
newAlpha = Math.min(100, Math.max(-100, newAlpha));
chart.options.chart.options3d.alpha = newAlpha;
window.alphaOn3dGraph = newAlpha;
window.betaOn3dGraph = newBeta;
chart.redraw(false);
},
'mouseup touchend': function () {
$(document).unbind('.hc');
}
});
});

Highcharts live chart shifts continuously

I have the following issue:
I initialize the chart with null values because I don't have meaningful initial values.
I add points with addPoint after getting the data through ajax
The problem is that the chart shifts continuously and not just on the end of the time range.
I use the code:
generateInitialData = function() {
var initialData = [];
var time = (new Date()).getTime();
var i;
for (i = -99; i <= 0; i++) {
initialData.push([time + i * 3000,null]);
}
return initialData;
}
addPoint = function(){
var max = 1000;
var min = 300;
var value = Math.floor(Math.random() * (max - min + 1)) + min;
chart.series[0].addPoint([(new Date()).getTime(), value], true, true);
}
$('#container').highcharts({
chart: {
type: 'line',
},
xAxis: {
type: 'datetime',
minRange:1000 * 60 * 5,
maxRange:1000 * 60 * 5
}
});
var chart = $('#container').highcharts();
chart.addSeries({"name": "example","data":generateInitialData()});
setInterval(addPoint,3000);
Please see jsFiddle demo of the issue :
http://jsfiddle.net/wXmLQ/7/
Please help
Tali
You don't need to set null data from the beginning, instead of that you can just addd one empty point and just check if you want to shift or not, see: http://jsfiddle.net/Fusher/wXmLQ/8/
addPoint = function(){
var max = 1000;
var min = 300;
var value = Math.floor(Math.random() * (max - min + 1)) + min;
var len = chart.series[0].data.length;
chart.series[0].addPoint([(new Date()).getTime(), value], len < 99);
}
$('#container').highcharts({
chart: {
type: 'line',
},
xAxis: {
type: 'datetime',
minTickInterval: 1000 * 60, //one minute
minRange:1000 * 60 * 5,
maxRange:1000 * 60 * 5
}
});
var chart = $('#container').highcharts();
chart.addSeries({"name": "example","data":[[+new Date(), null]]});
setInterval(addPoint,3000);
The addPoint and addSeries methods have a re-draw parameter that defaults to true. Make this parameter false whenever you call an updating method
chart.series[0].addPoint([(new Date()).getTime(), value], false);
chart.addSeries({"name": "example","data":generateInitialData()}, false);
Then once you've made all the updates, explicitly redraw the chart
chart.redraw();

Resources