Is there a way, in Highcharts, to create a network diagram where the connections are of width proportional to a data series (like a Sankey diagram?)
The application is a "traffic flow" visualisation (but between logical points, not physical ones, so overlaid onto a network diagram rather than a map.)
This is not supported by default, but you can easily get the wanted result by a small modification of getLinkAttribues method:
H.seriesTypes.networkgraph.prototype.pointClass.prototype.getLinkAttribues = function() {
var linkOptions = this.series.options.link,
maxValue,
pointOptions = this.options;
if (!this.linkWidth) {
this.series.points.forEach(function(p) {
maxValue = maxValue ?
Math.max(maxValue, p.options.value) :
p.options.value;
});
this.series.points.forEach(function(p) {
p.linkWidth = p.options.value * 10 / maxValue;
});
}
return {
'stroke-width': this.linkWidth || 1,
stroke: pointOptions.color || linkOptions.color,
dashstyle: pointOptions.dashStyle || linkOptions.dashStyle
};
}
Live demo: https://jsfiddle.net/BlackLabel/13zt8qds/
Docs: https://www.highcharts.com/docs/extending-highcharts
Related
I'm trying to create something similar to the image below. Where each column has a heading with it.
I know that chart.renderer.text can be used to create & place custom text on chart. However, I'm unable to find a way to fetch the column/node coordinates data(x,y) which would help me place them.
Also is there a programmatic way to do this task. For example, a function that fetches all the columns coordinates and populates all the headings from an existing list.
To summarize:
How to fetch a columns (x,y) Coordinates?
How to dynamically place headings for all columns from a list?
Image
You can get the required coordinates and place the headers in render event, for example:
events: {
render: function() {
var chart = this,
series = chart.series[0],
columns = series.nodeColumns,
isFirst,
isLast,
xPos;
if (!series.customHeaders) {
series.customHeaders = [];
}
columns.forEach(function(column, i) {
xPos = column[0].nodeX + chart.plotLeft;
isFirst = i === 0;
isLast = i === columns.length - 1;
if (!series.customHeaders[i]) {
series.customHeaders.push(
chart.renderer.text(
headers[i],
xPos,
80
).attr({
translateX: isFirst ? 0 : (
isLast ?
series.options.nodeWidth :
series.options.nodeWidth / 2
),
align: isFirst ? 'left' : (
isLast ? 'right' : 'center'
)
}).add()
)
} else {
series.customHeaders[i].attr({
x: xPos
});
}
});
}
}
Live demo: https://jsfiddle.net/BlackLabel/6Lvdufbp/
API Reference:
https://api.highcharts.com/highcharts/chart.events.render
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#text
I built a widget with multiple y Axes very similar to the official sample here: http://www.highcharts.com/stock/demo/candlestick-and-volume
I'm trying to have some sort of visual separation between the chart panes, either by
applying a special style to the maximum grid line for each pane, or
adding a horizontal line in the whitespace between the panes
The only approach i got working is using PlotLines, but I'd rather have a separator that's independent of zoom levels. Any ideas on how to achieve this?
Use Renderer to draw a path between y axes.
function drawSeparator() {
let separator = this.separator;
const options = this.options.chart.separator;
if (options && options.enabled) {
if (!separator) {
this.separator = separator = this.renderer.path({
d: 'M 0 0',
'stroke-width': options.width === undefined ? 1 : options.width,
stroke: options.color || 'black'
}).add();
}
const topAxisBottom = this.yAxis[0].top + this.yAxis[0].height;
const bottomAxisTop = this.yAxis[1].top;
const y = topAxisBottom + (bottomAxisTop - topAxisBottom) / 2;
separator.attr({
d: `M 0 ${y} L ${this.chartWidth} ${y}`
});
}
}
Call the method on load/redraw event
chart: {
events: {
load: drawSeparator,
redraw: drawSeparator
},
separator: {
enabled: true,
width: 3,
color: 'blue'
}
},
You can modify the path's d attribute, the path starts from axis.left and stops on axis.left + axis.width
Live example and output
http://jsfiddle.net/L11uqxgq/
I have an areaspline with only one series. The design calls for drawing x-axis gridlines that touch the series and don't extend beyond that. Is it possible to do this? Here's my code to configure the gridlines:
xAxis: {
gridLineWidth: 1,
tickAmount: 30,
lineWidth: 0,
labels: {
enabled: false
},
minPadding: 0
},
Another option (other then 2 mentioned by jlbriggs in a comment on the question) might be to extend Highcharts and change paths of grid lines in a wrapper like:
(function(H) {
var UNDEFINED;
H.wrap(H.Tick.prototype, 'render', function(p) {
p.apply(this, [].slice.call(arguments, 1)); //run original function
if (this.axis.isXAxis && this.gridLine) {
var point = this.axis.series[0].options.data[this.pos],
d = this.gridLine.attr('d').split(' ');
if (point !== UNDEFINED) {
d[2] = this.axis.chart.yAxis[0].toPixels(point);
d = d.join(' ');
} else {
d = ''; //remove if not crossing any point
}
this.gridLine.attr({
d: d
});
}
});
})(Highcharts)
Example: http://jsfiddle.net/69f5z3us/
If a grid line is not crossing any point, then the grid line is removed, but you could change that part of the code if you want to.
I want to make stacked bar chart where each portion has a width that encodes one value (say "Change" in the data below) and a height that encodes another value ("Share")
In some ways this is like a histogram with different bin sizes. There are a few "histogram" questions but none seem to address this. Plot Histograms in Highcharts
So given data like this:
Category Share Price Change
Apples 14.35 0.1314192423
Horseradish 46.168 0.1761474117
Figs 2.871 0.018874249
Tomatoes 13.954 0.0106121298
Mangoes 7.264 0.1217297011
Raisins 5.738 0.0206787136
Eggplant 6.31 0.0110160732
Other produce 3.344 0.0945377722
I can make a stacked bar that captures the "share" column in widths:
And another that captures the "change" column in heights:
And I can use an image editor to combine those into this histogram-like beast:
Which really captures that horseradish is a huge deal. So my question is, can I do that within Highcharts?
You can realise that by using snippet.
(function (H) {
var seriesTypes = H.seriesTypes,
each = H.each,
extendClass = H.extendClass,
defaultPlotOptions = H.getOptions().plotOptions,
merge = H.merge;
defaultPlotOptions.marimekko = merge(defaultPlotOptions.column, {
pointPadding: 0,
groupPadding: 0
});
seriesTypes.marimekko = extendClass(seriesTypes.column, {
type: 'marimekko',
pointArrayMap: ['y', 'z'],
parallelArrays: ['x', 'y', 'z'],
processData: function () {
var series = this;
this.totalZ = 0;
this.relZ = [];
seriesTypes.column.prototype.processData.call(this);
each(this.zData, function (z, i) {
series.relZ[i] = series.totalZ;
series.totalZ += z;
});
},
translate: function () {
var series = this,
totalZ = series.totalZ,
xAxis = series.xAxis;
seriesTypes.column.prototype.translate.call(this);
// Distort the points to reflect z dimension
each(this.points, function (point, i) {
var shapeArgs = point.shapeArgs,
relZ = series.relZ[i];
shapeArgs.x *= (relZ / totalZ) / (shapeArgs.x / xAxis.len);
shapeArgs.width *= (point.z / totalZ) / (series.pointRange / series.xAxis.max);
});
}
});
}(Highcharts));
Example: http://jsfiddle.net/highcharts/75oucp3b/
I'm trying to set the min zoom (max range) of my chart. Basically I'm trying to do the opposite of the minRange property. I'm struggling for a while with this problem. I have a "solution", but I don't like it, this solution allow the user to choose a range greater then the "max range", and immediately correct it.
POSSIBLE SOLUTION
$(function() {
var lastMin;
var lastMax;
var maxRange = 12 * 30 * 24 * 3600 * 1000; //12 month
$('#container').highcharts('StockChart', {
scrollbar: {
liveRedraw: false
},
xAxis: {
events: {
afterSetExtremes: function(e) {
var max = this.max,
min = this.min;
if (lastMin && lastMax) {
if(max-min > maxRange) {
if (min < lastMin) {
min = max - maxRange;
} else {
max = min + maxRange;
}
}
}
var x = this;
setTimeout(function(){
x.setExtremes(min,max); //chart xAxis
}, 1);
lastMin = min;
lastMax = max;
}
}
},
rangeSelector: {
selected: 1
},
series: [{
name: 'USD to EUR',
data: usdeur
}]
});
});
I want to block the user from choosing a range greater than the allowed, in other words, block the navigator when it's too big
I'm also following this issue, I tried all the proposed solution, but I'm having errors ("Uncaught ReferenceError: Highcharts is not defined")
Thanks Sebastian!
I managed to find a solution (fiddle) wrapping the "render" function. Doing that I managed to really set a "min zoom" on the navigator bar.
$(function() {
var lastX0;
var lastX1;
var maxRange = 100; //100 pixels
(function (H) {
H.wrap(H.Scroller.prototype, 'render', function (proceed) {
console.log(arguments)
if(arguments[4] - arguments[3] > maxRange + 2) {
if (arguments[3] < lastX0) {
arguments[3] = lastX0;
} else {
arguments[4] = lastX1;
}
}
proceed.apply(this, [].slice.call(arguments, 1));
lastX0 = arguments[3];
lastX1 = arguments[4];
});
}(Highcharts));
$('#container').highcharts('StockChart', {
scrollbar: {
liveRedraw: true
},
series: [{
name: 'USD to EUR',
data: usdeur
}]
});
var highchart = $('#container').highcharts();
var extremes = highchart.xAxis[0].getExtremes();
var rangeTotal = extremes.max - extremes.min;
var f = maxRange / $('#container').width();
highchart.xAxis[0].setExtremes(extremes.max - (f * rangeTotal), extremes.max);
});
In the sample code I'm used a fixed amount of pixels, but in my real application i'm making it dynamic. I making this, because I can't use the data grouping property in the software that I'm working, and since the minimum size of a bar in a chart is 1 pixel (obviously) highcharts hide some bars (or points).
I'm setting the minimum zoom so all bar in the displayed range are visible , since the user can't display a higher range in the x Axis the "hidden" bar "problem" (is an awesome feature, but I can't make use of it) won't happen