Highstock: export CVS/XML only visible/selected range - highcharts

I'm facing a problem when import Highstock. I'd like to import csv/xml in a highstock graph.
It's work fine but is getting the total of data present in chart. I'd like to export CVS/XML only visible/selected range.
Here what I found in Highstock forum:
https://www.highcharts.com/forum/viewtopic.php?f=12&t=41056
jsfiddle: jsfiddle example
The example in JSFiddle works but is getting an number in Unix Timestemp.
var defined = Highcharts.defined,
each = Highcharts.each,
pick = Highcharts.pick,
win = Highcharts.win,
doc = win.document,
seriesTypes = Highcharts.seriesTypes,
downloadAttrSupported = doc.createElement('a').download !== undefined;
Highcharts.Chart.prototype.getDataRows = function (multiLevelHeaders) {
var time = this.time,
csvOptions = (this.options.exporting && this.options.exporting.csv) ||
{},
xAxis,
xAxes = this.xAxis,
rows = {},
rowArr = [],
dataRows,
topLevelColumnTitles = [],
columnTitles = [],
columnTitleObj,
i,
x,
xTitle,
// Options
columnHeaderFormatter = function (item, key, keyLength) {
if (csvOptions.columnHeaderFormatter) {
var s = csvOptions.columnHeaderFormatter(item, key, keyLength);
if (s !== false) {
return s;
}
}
if (!item) {
return 'Category';
}
if (item instanceof Highcharts.Axis) {
return (item.options.title && item.options.title.text) ||
(item.isDatetimeAxis ? 'DateTime' : 'Category');
}
if (multiLevelHeaders) {
return {
columnTitle: keyLength > 1 ? key : item.name,
topLevelColumnTitle: item.name
};
}
return item.name + (keyLength > 1 ? ' (' + key + ')' : '');
},
xAxisIndices = [];
// Loop the series and index values
i = 0;
this.setUpKeyToAxis();
each(this.series, function (series) {
var keys = series.options.keys,
pointArrayMap = keys || series.pointArrayMap || ['y'],
valueCount = pointArrayMap.length,
xTaken = !series.requireSorting && {},
categoryMap = {},
datetimeValueAxisMap = {},
xAxisIndex = Highcharts.inArray(series.xAxis, xAxes),
mockSeries,
j;
// Map the categories for value axes
each(pointArrayMap, function (prop) {
var axisName = (
(series.keyToAxis && series.keyToAxis[prop]) ||
prop
) + 'Axis';
categoryMap[prop] = (
series[axisName] &&
series[axisName].categories
) || [];
datetimeValueAxisMap[prop] = (
series[axisName] &&
series[axisName].isDatetimeAxis
);
});
if (
series.options.includeInCSVExport !== false &&
!series.options.isInternal &&
series.visible !== false // #55
) {
// Build a lookup for X axis index and the position of the first
// series that belongs to that X axis. Includes -1 for non-axis
// series types like pies.
if (!Highcharts.find(xAxisIndices, function (index) {
return index[0] === xAxisIndex;
})) {
xAxisIndices.push([xAxisIndex, i]);
}
// Compute the column headers and top level headers, usually the
// same as series names
j = 0;
while (j < valueCount) {
columnTitleObj = columnHeaderFormatter(
series,
pointArrayMap[j],
pointArrayMap.length
);
columnTitles.push(
columnTitleObj.columnTitle || columnTitleObj
);
if (multiLevelHeaders) {
topLevelColumnTitles.push(
columnTitleObj.topLevelColumnTitle || columnTitleObj
);
}
j++;
}
mockSeries = {
chart: series.chart,
autoIncrement: series.autoIncrement,
options: series.options,
pointArrayMap: series.pointArrayMap
};
// Export directly from options.data because we need the uncropped
// data (#7913), and we need to support Boost (#7026).
each(series.options.data, function eachData(options, pIdx) {
var key,
prop,
val,
name,
point;
point = { series: mockSeries };
series.pointClass.prototype.applyOptions.apply(
point,
[options]
);
if (point.x >= series.xAxis.min && point.x <= series.xAxis.max) {
key = point.x;
name = series.data[pIdx] && series.data[pIdx].name;
if (xTaken) {
if (xTaken[key]) {
key += '|' + pIdx;
}
xTaken[key] = true;
}
j = 0;
// Pies, funnels, geo maps etc. use point name in X row
if (!series.xAxis || series.exportKey === 'name') {
key = name;
}
//console.log(point)
if (!rows[key]) {
// Generate the row
rows[key] = [];
// Contain the X values from one or more X axes
rows[key].xValues = [];
}
rows[key].x = point.x;
rows[key].name = name;
rows[key].xValues[xAxisIndex] = point.x;
while (j < valueCount) {
prop = pointArrayMap[j]; // y, z etc
val = point[prop];
rows[key][i + j] = pick(
categoryMap[prop][val], // Y axis category if present
datetimeValueAxisMap[prop] ?
time.dateFormat(csvOptions.dateFormat, val) :
null,
val
);
j++;
}
}
});
i = i + j;
}
});
// Make a sortable array
for (x in rows) {
if (rows.hasOwnProperty(x)) {
rowArr.push(rows[x]);
}
}
var xAxisIndex, column;
// Add computed column headers and top level headers to final row set
dataRows = multiLevelHeaders ? [topLevelColumnTitles, columnTitles] :
[columnTitles];
i = xAxisIndices.length;
while (i--) { // Start from end to splice in
xAxisIndex = xAxisIndices[i][0];
column = xAxisIndices[i][1];
xAxis = xAxes[xAxisIndex];
// Sort it by X values
rowArr.sort(function (a, b) { // eslint-disable-line no-loop-func
return a.xValues[xAxisIndex] - b.xValues[xAxisIndex];
});
// Add header row
xTitle = columnHeaderFormatter(xAxis);
dataRows[0].splice(column, 0, xTitle);
if (multiLevelHeaders && dataRows[1]) {
// If using multi level headers, we just added top level header.
// Also add for sub level
dataRows[1].splice(column, 0, xTitle);
}
// Add the category column
each(rowArr, function (row) { // eslint-disable-line no-loop-func
var category = row.name;
if (xAxis && !defined(category)) {
if (xAxis.isDatetimeAxis) {
if (row.x instanceof Date) {
row.x = row.x.getTime();
}
category = time.dateFormat(
csvOptions.dateFormat,
row.x
);
} else if (xAxis.categories) {
category = pick(
xAxis.names[row.x],
xAxis.categories[row.x],
row.x
);
} else {
category = row.x;
}
}
// Add the X/date/category
row.splice(column, 0, category);
});
}
dataRows = dataRows.concat(rowArr);
Highcharts.fireEvent(this, 'exportData', { dataRows: dataRows });
return dataRows;
};
var chart = new Highcharts.StockChart({
chart: {
renderTo: 'container'
},
navigator: {
series: {
includeInCSVExport: false
}
},
series: [{
data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4, 32, 221, 123, 321, 322, 29, 177, 76, 46, 245, 122, 67],
pointStart: Date.UTC(2013, 0, 1),
pointInterval: 24 * 36e5
}],
exporting: {
csv: {
dateFormat: '%Y-%m-%d'
}
}
});
document.getElementById('getcsv').addEventListener('click', function () {
alert(chart.getCSV());
});
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<div id="container" style="height: 300px; margin-top: 2em"></div>
<button id="getcsv">Alert CSV</button>

I got the answer.
Just include this condition inside of export-data in funciton Highcharts.Chart.prototype.getDataRows:
(...)
point = { series: mockSeries };
series.pointClass.prototype.applyOptions.apply(point, [options]);
key = point.x;
name = series.data[pIdx] && series.data[pIdx].name;
j = 0;
if (point.x >= series.xAxis.min && point.x <= series.xAxis.max) {// ***this condition
if (!xAxis || series.exportKey === "name" || (!hasParallelCoords && xAxis && xAxis.hasNames && name)) {
key = name;
}
(...)
}
If don't want change the lib following this example in fiddleJs
example Highstock period time export

Related

How to edit "highcharts-label-icon" in highcharts-gantt

Highchart gantt : https://www.highcharts.com/docs/gantt/getting-started-gantt
I want to change this dropdown btn icon to right of text label.
How to edit it?
Jsfiddle : https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/gantt/demo/project-management
This is not possible from the API but you can achieve that by wrapping the renderLabel function.
(function(H) {
const {
addEvent,
isNumber,
isObject,
pick
} = H,
onTickHoverExit = function(label, options) {
var css = isObject(options.style) ? options.style : {};
label.removeClass('highcharts-treegrid-node-active');
if (!label.renderer.styledMode) {
label.css({
textDecoration: css.textDecoration
});
}
},
onTickHover = function(label) {
label.addClass('highcharts-treegrid-node-active');
if (!label.renderer.styledMode) {
label.css({
textDecoration: 'underline'
});
}
},
renderLabelIcon = function(tick, params) {
var treeGrid = tick.treeGrid,
isNew = !treeGrid.labelIcon,
renderer = params.renderer,
labelBox = params.xy,
options = params.options,
width = options.width || 0,
height = options.height || 0,
iconCenter = {
x: labelBox.x + tick.label.getBBox().width + width,
y: labelBox.y - (height / 2)
},
rotation = params.collapsed ? 90 : 180,
shouldRender = params.show && isNumber(iconCenter.y);
var icon = treeGrid.labelIcon;
if (!icon) {
treeGrid.labelIcon = icon = renderer
.path(renderer.symbols['circle'](options.x || 0, options.y || 0, width, height))
.addClass('highcharts-label-icon')
.add(params.group);
}
// Set the new position, and show or hide
icon[shouldRender ? 'show' : 'hide'](); // #14904, #1338
// Presentational attributes
if (!renderer.styledMode) {
icon
.attr({
cursor: 'pointer',
'fill': pick(params.color, "#666666" /* Palette.neutralColor60 */ ),
'stroke-width': 1,
stroke: options.lineColor,
strokeWidth: options.lineWidth || 0
});
}
// Update the icon positions
icon[isNew ? 'attr' : 'animate']({
translateX: iconCenter.x,
translateY: iconCenter.y,
rotation: rotation
});
};
H.wrap(H.Tick.prototype, 'renderLabel', function(proceed) {
var tick = this,
pos = tick.pos,
axis = tick.axis,
label = tick.label,
mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode,
options = axis.options,
labelOptions = pick(tick.options && tick.options.labels, options && options.labels),
symbolOptions = (labelOptions && isObject(labelOptions.symbol, true) ?
labelOptions.symbol : {}),
node = mapOfPosToGridNode && mapOfPosToGridNode[pos],
level = node && node.depth,
isTreeGrid = options.type === 'treegrid',
shouldRender = axis.tickPositions.indexOf(pos) > -1,
prefixClassName = 'highcharts-treegrid-node-',
styledMode = axis.chart.styledMode;
var collapsed,
addClassName,
removeClassName;
if (isTreeGrid && node) {
// Add class name for hierarchical styling.
if (label &&
label.element) {
label.addClass(prefixClassName + 'level-' + level);
}
}
proceed.apply(tick, Array.prototype.slice.call(arguments, 1));
if (isTreeGrid &&
label &&
label.element &&
node &&
node.descendants &&
node.descendants > 0) {
collapsed = axis.treeGrid.isCollapsed(node);
renderLabelIcon(tick, {
color: (!styledMode &&
label.styles &&
label.styles.color ||
''),
collapsed: collapsed,
group: label.parentGroup,
options: symbolOptions,
renderer: label.renderer,
show: shouldRender,
xy: label.xy
});
// Add class name for the node.
addClassName = prefixClassName +
(collapsed ? 'collapsed' : 'expanded');
removeClassName = prefixClassName +
(collapsed ? 'expanded' : 'collapsed');
label
.addClass(addClassName)
.removeClass(removeClassName);
if (!styledMode) {
label.css({
cursor: 'pointer'
});
}
// Add events to both label text and icon
[label, tick.treeGrid.labelIcon].forEach(function(object) {
if (object && !object.attachedTreeGridEvents) {
// On hover
addEvent(object.element, 'mouseover', function() {
onTickHover(label);
});
// On hover out
addEvent(object.element, 'mouseout', function() {
onTickHoverExit(label, labelOptions);
});
addEvent(object.element, 'click', function() {
tick.treeGrid.toggleCollapse();
});
object.attachedTreeGridEvents = true;
}
});
}
});
}(Highcharts))
Demo:
https://jsfiddle.net/BlackLabel/k2rb6zgy/
Extending Highcharts:
https://www.highcharts.com/docs/extending-highcharts/extending-highcharts

Custom "compare" and axis range in Highcharts

I added a custom "compare" routine in order to compute the difference of some point value relative to the previous point (instead of relative to the first point in the series as implemented in compare: 'value'):
Highcharts.wrap(Highcharts.Series.prototype, "setCompare", function(proceed, compare) {
// Set or unset the modifyValue method
this.modifyValue = (compare === 'value' || compare === 'percent' || compare === 'value_previous' || compare === 'percent_previous') ?
function(value, point) {
// MODIFIED ---------------------
var compareValue;
if (point && compare.includes("_previous")) {
compareValue = point.series.processedYData[point.index - 1];
} else {
compareValue = this.compareValue;
}
// -------------------------------
if (typeof value !== 'undefined' && typeof compareValue !== 'undefined') { // #2601, #5814
// Get the modified value
if (compare.includes('value')) { // MODIFIED!!!!!!!
value -= compareValue;
// Compare percent
} else {
value = 100 * (value / compareValue) - (this.options.compareBase === 100 ? 0 : 100);
}
// record for tooltip etc.
if (point) {
point.change = value;
}
return value;
}
return 0;
} :
null;
// Survive to export, #5485
this.userOptions.compare = compare; // ---WHAT TO DO???---
// Mark dirty
if (this.chart.hasRendered) {
this.isDirty = true;
}
});
The chart is drawn as expected (see fiddle), but the yaxis' range does not cover the new computed values. Any idea how to solve the problem?
Fiddle
You need also include this part of the code which will trigger and get the this.modifyValue from your custom wrap.
///
/// MISSED FUNCTIONS
///
var arrayMin = Highcharts.arrayMin = function arrayMin(data) {
var i = data.length, min = data[0];
while (i--) {
if (data[i] < min) {
min = data[i];
}
}
return min;
};
var arrayMax = Highcharts.arrayMax = function arrayMax(data) {
var i = data.length, max = data[0];
while (i--) {
if (data[i] > max) {
max = data[i];
}
}
return max;
};
// Modify series extremes
Highcharts.addEvent(Highcharts.Series.prototype, 'afterGetExtremes', function (e) {
var dataExtremes = e.dataExtremes;
if (this.modifyValue && dataExtremes) {
var extremes = [
this.modifyValue(dataExtremes.dataMin),
this.modifyValue(dataExtremes.dataMax)
];
dataExtremes.dataMin = arrayMin(extremes);
dataExtremes.dataMax = arrayMax(extremes);
}
});
///
///
///
Demo: https://jsfiddle.net/BlackLabel/ucvae7xy/

Highcharts line chart with a connector between series and label

I am using the Highcharts Editor with some Custom code and I have a line chart with over 20 series. Each series has a label at the end (series name). Because the series are so close to each other, it is impossible to show all the corresponding labels as they overlap. I'd like to enable a connector line between each series and corresponding label.
I tried connectorAllowed: true and enabled: true but from the JS API Reference web-page these options only apply to pie charts.
Here is my code:
<div id="highcharts-3fa2171d-2ddd-49f5-898d-daef17b342b4"></div><script>
(function(){ var files = ["https://code.highcharts.com/stock/highstock.js","https://code.highcharts.com/highcharts-more.js","https://code.highcharts.com/highcharts-3d.js","https://code.highcharts.com/modules/data.js","https://code.highcharts.com/modules/exporting.js","https://code.highcharts.com/modules/funnel.js","https://code.highcharts.com/modules/annotations.js","https://code.highcharts.com/modules/solid-gauge.js"],loaded = 0; if (typeof window["HighchartsEditor"] === "undefined") {window.HighchartsEditor = {ondone: [cl],hasWrapped: false,hasLoaded: false};include(files[0]);} else {if (window.HighchartsEditor.hasLoaded) {cl();} else {window.HighchartsEditor.ondone.push(cl);}}function isScriptAlreadyIncluded(src){var scripts = document.getElementsByTagName("script");for (var i = 0; i < scripts.length; i++) {if (scripts[i].hasAttribute("src")) {if ((scripts[i].getAttribute("src") || "").indexOf(src) >= 0 || (scripts[i].getAttribute("src") === "http://code.highcharts.com/highcharts.js" && src === "https://code.highcharts.com/stock/highstock.js")) {return true;}}}return false;}function check() {if (loaded === files.length) {for (var i = 0; i < window.HighchartsEditor.ondone.length; i++) {try {window.HighchartsEditor.ondone[i]();} catch(e) {console.error(e);}}window.HighchartsEditor.hasLoaded = true;}}function include(script) {function next() {++loaded;if (loaded < files.length) {include(files[loaded]);}check();}if (isScriptAlreadyIncluded(script)) {return next();}var sc=document.createElement("script");sc.src = script;sc.type="text/javascript";sc.onload=function() { next(); };document.head.appendChild(sc);}function each(a, fn){if (typeof a.forEach !== "undefined"){a.forEach(fn);}else{for (var i = 0; i < a.length; i++){if (fn) {fn(a[i]);}}}}var inc = {},incl=[]; each(document.querySelectorAll("script"), function(t) {inc[t.src.substr(0, t.src.indexOf("?"))] = 1; }); function cl() {if(typeof window["Highcharts"] !== "undefined"){var options={"title":{"text":"30-day female mortality rates at age 45"},"subtitle":{"text":"Calendar period: 1996 - 2014"},"exporting":{},"chart":{"type":"line","height":null,"ignoreHiddenSeries":true,"showAxes":false,"spacingRight":75,"spacingTop":10},"series":[{"name":"NHL","turboThreshold":0,"_colorIndex":0,"_symbolIndex":0,"dataLabels":{"allowOverlap":false},"marker":{},"color":null},{"name":"bladder","turboThreshold":0,"_colorIndex":1,"_symbolIndex":1},{"name":"brain","turboThreshold":0,"_colorIndex":2,"_symbolIndex":2},{"name":"breast","turboThreshold":0,"_colorIndex":3,"_symbolIndex":3},{"name":"cervix","turboThreshold":0,"_colorIndex":4,"_symbolIndex":4},{"name":"colon","turboThreshold":0,"_colorIndex":5,"_symbolIndex":0},{"name":"hodgkin","turboThreshold":0,"_colorIndex":6,"_symbolIndex":1},{"name":"kidney","turboThreshold":0,"_colorIndex":7,"_symbolIndex":2},{"name":"larynx","turboThreshold":0,"_colorIndex":8,"_symbolIndex":3},{"name":"leukaemia","turboThreshold":0,"_colorIndex":9,"_symbolIndex":4,"marker":{},"color":"#546e7a"},{"name":"liver","turboThreshold":0,"_colorIndex":0,"_symbolIndex":0},{"name":"lung","turboThreshold":0,"_colorIndex":1,"_symbolIndex":1,"dataLabels":{"allowOverlap":true,"enabled":true},"allowOverlapX":false},{"name":"melanoma","turboThreshold":0,"_colorIndex":2,"_symbolIndex":2},{"name":"mesothelioma","turboThreshold":0,"_colorIndex":3,"_symbolIndex":3},{"name":"myeloma","turboThreshold":0,"_colorIndex":4,"_symbolIndex":4},{"name":"oesophagus","turboThreshold":0,"_colorIndex":5,"_symbolIndex":0},{"name":"ovary","turboThreshold":0,"_colorIndex":6,"_symbolIndex":1},{"name":"pancreas","turboThreshold":0,"_colorIndex":7,"_symbolIndex":2},{"name":"rectum","turboThreshold":0,"_colorIndex":8,"_symbolIndex":3,"dataLabels":{"allowOverlap":true}},{"name":"stomach","turboThreshold":0,"_colorIndex":9,"_symbolIndex":4,"marker":{},"color":"#651fff"},{"name":"thyroid","turboThreshold":0,"_colorIndex":0,"_symbolIndex":0,"marker":{},"color":"#cddc39"},{"name":"uterus","turboThreshold":0,"_colorIndex":1,"_symbolIndex":1,"marker":{},"color":"#d500f9"}],"plotOptions":{"series":{"dataLabels":{"enabled":true},"animation":false},"line":{"dataLabels":{"allowOverlap":true,"enabled":false,"inside":false,"shadow":false,"align":"left","crop":false,"padding":5,"verticalAlign":"middle","overflow":"none","rotation":0,"x":2,"y":0,"zIndex":6},"label":{"connectorNeighbourDistance":24,"connectorAllowed":true,"enabled":true,"onArea":false},"marker":{"enabled":true}}},"pane":{"background":[]},"responsive":{"rules":[]},"xAxis":[{"type":"category","tickmarkPlacement":"on","title":{"text":"Calendar period"},"labels":{},"opposite":false}],"yAxis":[{"title":{"text":"Mortality"},"labels":{},"type":"linear"}],"legend":{"layout":"vertical","align":"right","enabled":false,"floating":false},"credits":{"enabled":false},"tooltip":{},"data":{"csv":"\"year\";\"NHL\";\"bladder\";\"brain\";\"breast\";\"cervix\";\"colon\";\"hodgkin\";\"kidney\";\"larynx\";\"leukaemia\";\"liver\";\"lung\";\"melanoma\";\"mesothelioma\";\"myeloma\";\"oesophagus\";\"ovary\";\"pancreas\";\"rectum\";\"stomach\";\"thyroid\";\"uterus\"\n1996;2.2;1;4.8;0.7;1.3;2.5;1;2.9;1.2;3.8;7.5;5.5;0.2;2.4;2.8;2.4;2.9;6.6;1.4;3.6;1.5;0.7\n1997;2.1;1;4.6;0.6;1.3;2.3;1;2.7;1.1;3.7;7.2;5.3;0.2;2.3;2.6;2.3;2.8;6.4;1.3;3.5;1.4;0.7\n1998;2;1;4.4;0.6;1.2;2.2;1;2.6;1.1;3.5;6.9;5.1;0.2;2.2;2.4;2.2;2.7;6.2;1.3;3.3;1.4;0.7\n1999;1.9;1;4.2;0.5;1.2;2.1;0.9;2.4;1;3.4;6.7;4.9;0.2;2.1;2.2;2.1;2.6;6.1;1.2;3.2;1.3;0.6\n2000;1.9;1;4;0.5;1.2;2;0.9;2.3;1;3.3;6.4;4.7;0.2;2.1;2.1;2;2.5;5.9;1.2;3.1;1.3;0.6\n2001;1.8;1;3.8;0.5;1.1;1.9;0.8;2.1;1;3.2;6.2;4.5;0.2;2;1.9;2;2.5;5.8;1.1;3;1.2;0.6\n2002;1.7;1;3.6;0.4;1.1;1.8;0.8;2;0.9;3.1;6;4.4;0.2;1.9;1.8;1.9;2.4;5.6;1.1;2.9;1.2;0.5\n2003;1.7;1;3.5;0.4;1.1;1.7;0.8;1.9;0.9;3;5.8;4.2;0.2;1.9;1.6;1.8;2.3;5.5;1;2.8;1.1;0.5\n2004;1.6;0.9;3.3;0.4;1;1.6;0.7;1.8;0.9;2.9;5.6;4.1;0.1;1.8;1.5;1.7;2.2;5.3;1;2.7;1.1;0.5\n2005;1.5;0.9;3.1;0.3;1;1.6;0.7;1.7;0.8;2.8;5.4;3.9;0.1;1.7;1.4;1.7;2.1;5.2;0.9;2.6;1;0.5\n2006;1.5;0.9;3;0.3;1;1.5;0.7;1.6;0.8;2.7;5.2;3.8;0.1;1.7;1.3;1.6;2;5.1;0.9;2.5;1;0.5\n2007;1.4;0.9;2.8;0.3;0.9;1.4;0.6;1.5;0.8;2.6;5;3.6;0.1;1.6;1.2;1.5;2;4.9;0.9;2.4;0.9;0.4\n2008;1.4;0.9;2.7;0.3;0.9;1.3;0.6;1.4;0.7;2.5;4.8;3.5;0.1;1.6;1.1;1.5;1.9;4.8;0.8;2.3;0.9;0.4\n2009;1.3;0.9;2.6;0.2;0.9;1.3;0.6;1.3;0.7;2.5;4.6;3.4;0.1;1.5;1;1.4;1.8;4.7;0.8;2.2;0.9;0.4\n2010;1.3;0.9;2.5;0.2;0.8;1.2;0.6;1.3;0.7;2.4;4.4;3.2;0.1;1.5;1;1.4;1.8;4.6;0.7;2.2;0.8;0.4\n2011;1.2;0.9;2.3;0.2;0.8;1.2;0.5;1.2;0.6;2.3;4.3;3.1;0.1;1.4;0.9;1.3;1.7;4.4;0.7;2.1;0.8;0.4\n2012;1.2;0.9;2.2;0.2;0.8;1.1;0.5;1.1;0.6;2.2;4.1;3;0.1;1.4;0.8;1.3;1.6;4.3;0.7;2;0.8;0.4\n2013;1.1;0.9;2.1;0.2;0.7;1;0.5;1;0.6;2.2;4;2.9;0.1;1.3;0.8;1.2;1.6;4.2;0.6;1.9;0.7;0.3\n2014;1.1;0.9;2;0.2;0.7;1;0.5;1;0.6;2.1;3.8;2.8;0.1;1.3;0.7;1.2;1.5;4.1;0.6;1.9;0.7;0.3","googleSpreadsheetKey":false,"googleSpreadsheetWorksheet":false}};
Highcharts.merge(true, options, {
plotOptions: {
series: {
dataLabels: {
enabled: true,
connectorAllowed: true,
formatter: function () {
// if last point
if(this.point === this.series.data[this.series.data.length-1]) {
return '<span style="color:'+this.series.color+'">'+this.series.name;
}
}
}
}
}
});
new Highcharts.Chart("highcharts-3fa2171d-2ddd-49f5-898d-daef17b342b4",
options);}}})();
</script>
I would like to have a connector line between each series and the corresponding label in order to distinguish between each series and their names.
Your help is much appreciated!!!
Here is a JSfiddle link with my code.

More than 100 points and one polyline, Google Maps v3

I'm working with google maps api v3 and I'm trying to use snap to road with more than 100 points but in addition end up with just one polyline with the whole route that I can put a small animation. The view is a html.erb.
var apiKey = any_key;
var map = handler.getMap();
var drawingManager;
var placeIdArray = [];
var snappedCoordinates = [];
var path = <%= raw(#locations) %>
var markers = <%= raw(#markers) %>
var centerOn = path[0].split(',');
function breadCrumbsGrapher(path) {
handler.removeMarkers(Gmaps.store.markers);
for(var i = 0; i < polylines.length; i++) {
polylines[i].setMap(null);
}
var divided = handlePath(path);
if (typeof divided[0] == 'object') {
for(var i = 0; i < divided.length; i++) {
runSnapToRoad(divided[i]);
}
} else {
runSnapToRoad(path);
}
}
function waypointsLimiter(path) {
var path_loc_size = path.length;
var limited = [];
if(path_loc_size > 30) {
var stepper = Math.ceil(path_loc_size/30);
for(var i = stepper; i < path_loc_size; i += stepper) {
limited.push(path[i]);
}
if(limited.indexOf(path[path_loc_size-1]) == -1) {
limited.push(path[path_loc_size-1]);
}
} else {
limited = path;
}
return limited;
}
function handlePath(path) {
var i = 0;
var j = path.length;
if (j > 100) {
var newArray = [],
chunk = j/2;
if (j >= 200) {
chunk = j/3;
} else if (j >= 300) {
chunk = j/4;
} else if (j >= 400) {
chunk = j/5;
} else if (j >= 500 ) {
chunk = j/6;
} else if (j >= 600) {
chunk = j/7;
} else if (j >= 700 || j <= 799) {
chunk = j/8;
} else {
alert('La ruta no puede ser mostrada');
}
for (i, j; i < j; i+=chunk) {
newArray.push(path.slice(i,i+chunk+1));
}
return newArray;
} else {
return path;
}
}
// Snap a user-created polyline to roads and draw the snapped path
function runSnapToRoad(path) {
var path = path.join('|');
$.get('https://roads.googleapis.com/v1/snapToRoads', {
interpolate: true,
key: apiKey,
path: path,
}, function(data) {
processSnapToRoadResponse(data);
drawSnappedPolyline();
});
}
// Store snapped polyline returned by the snap-to-road service.
function processSnapToRoadResponse(data) {
snappedCoordinates = [];
placeIdArray = [];
for (var i = 0; i < data.snappedPoints.length; i++) {
var latlng = new google.maps.LatLng(
data.snappedPoints[i].location.latitude,
data.snappedPoints[i].location.longitude);
snappedCoordinates.push(latlng);
placeIdArray.push(data.snappedPoints[i].placeId);
}
}
// Draws the snapped polyline (after processing snap-to-road response).
function drawSnappedPolyline() {
var symbol = {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
scale: 3,
strokeColor: '#3B16B3'
};
var snappedPolyline = new google.maps.Polyline({
path: snappedCoordinates,
strokeColor: '#E51E25',
strokeWeight: 3,
icons: [{
icon: symbol,
offset: '0%'
}]
});
snappedPolyline.setMap(map);
animate(snappedPolyline);
zoomToObject(snappedPolyline);
polylines.push(snappedPolyline);
}
function zoomToObject(obj){
var bounds = new google.maps.LatLngBounds();
var points = obj.getPath().getArray();
for (var n = 0; n < points.length ; n++){
bounds.extend(points[n]);
}
map.fitBounds(bounds);
}
function animate(line) {
var count = 0;
window.setInterval(function() {
count = (count + 1) % 600;
var icons = line.get('icons');
icons[0].offset = (count / 6) + '%';
line.set('icons', icons);
}, 70);
}
breadCrumbsGrapher(path);
Also I've tried declaring a variable outside so I can concat all of the coordinates and generate a polyline with it but doesn't seem to work. Actualy that big array ends up being of 2000+ points.
The result that I have with the provided code
After all of that the issue is that I don't know how to merge the polylines to have just one line and being able to animate just that one line. If there's more than 100 coordinates I plot more polylines. In the image you can see that there's 3 icons (one for each polyline) and I need just to draw one line and have 1 icon.
To reproduce the issue just add a key and if you want use this set of coordinates:
https://drive.google.com/file/d/1jLb7Djv5DiSdR3k4QZRSatXBwrohlxcI/view?usp=sharing
function breadCrumbsGrapher(path) {
//mapMarkers();
snappedCoordinates = [];
handler.removeMarkers(Gmaps.store.markers);
for(var i = 0; i < polylines.length; i++) {
polylines[i].setMap(null);
}
var divided = handlePath(path);
if (typeof divided[0] == 'object') {
for(var i = 0; i < divided.length; i++) {
runSnapToRoad(divided[i]);
}
} else {
runSnapToRoad(path);
}
console.log(snappedCoordinates);
drawSnappedPolyline();
}
function runSnapToRoad(path) {
var path = path.join('|');
$.get('https://roads.googleapis.com/v1/snapToRoads', {
interpolate: true,
key: apiKey,
path: path,
}, function(data) {
processSnapToRoadResponse(data);
//drawSnappedPolyline();
});
}
I've changed the code but it doesn't work, even though I end up with a 2,557 coordinates array.
I've tried also tried this thinking that this could give me the time to have all coordinates:
async function breadCrumbsGrapher(path) {
//mapMarkers();
snappedCoordinates = [];
handler.removeMarkers(Gmaps.store.markers);
for(var i = 0; i < polylines.length; i++) {
polylines[i].setMap(null);
}
var divided = handlePath(path);
if (typeof divided[0] == 'object') {
for(var i = 0; i < divided.length; i++) {
await runSnapToRoad(divided[i]);
}
} else {
await runSnapToRoad(path);
}
console.log(snappedCoordinates);
drawSnappedPolyline();
}
and:
async function runSnapToRoad(path) {
var path = path.join('|');
await $.get('https://roads.googleapis.com/v1/snapToRoads', {
interpolate: true,
key: apiKey,
path: path,
}, function(data) {
processSnapToRoadResponse(data);
});
}
Thank you in advance.
You are using $.get() to query the Roads API which is an asynchronous call so when you call drawSnappedPolyline() from within your breadCrumbsGrapher function, you most probably don't (yet) have all coordinates in return from your AJAX call(s).
If you have 550 coordinates in your original path, you then know you must call the Roads API 6 times (5 times with 100 points + 1 time with 50 points). You should then be able to set a counter somewhere, to only draw your (complete) Polyline, once you got your 6 responses from the Roads API.
Or you could base that on the length of your final array (compared to the original path) although I don't know what would happen in case some points can't be "snapped".

Highcharts: how to select column (crosshairs) on mouseover xAxis labels

How I can select column (crosshairs) on mouseover xAxis labels?
I can mouseover label, but don't know how to select column.
$(document).on('mouseover', '.highcharts-axis:eq(' + (axisCount - 1) + ') text, .highcharts-axis-labels:eq(' + (axisCount - 1) + ') text', function () {
console.log('mouseover');
// hover current column - crosshairs
});
http://jsfiddle.net/o355e82b/3/ (image how it should be - inside)
You can wrap crosshairs function and modify height of plotted element.
(function (HC) {
HC.wrap(HC.Axis.prototype, 'drawCrosshair', function (proceed, e, point) {
var path,
options = this.crosshair,
animation = options.animation,
pos,
attribs,
categorized;
if (
// Disabled in options
!this.crosshair ||
// Snap
((defined(point) || !HC.pick(this.crosshair.snap, true)) === false)) {
this.hideCrosshair();
} else {
// Get the path
if (!HC.pick(options.snap, true)) {
pos = (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos);
} else if (defined(point)) {
pos = this.isXAxis ? point.plotX : this.len - point.plotY; // #3834
}
if (this.isRadial) {
path = this.getPlotLinePath(this.isXAxis ? point.x : pick(point.stackY, point.y)) || null; // #3189
} else {
path = this.getPlotLinePath(null, null, null, null, pos) || null; // #3189
}
if (path === null) {
this.hideCrosshair();
return;
}
// Draw the cross
if (this.cross) {
//overwrite a height
path[5] = point.series.chart.containerHeight;
this.cross.attr({
visibility: VISIBLE
})[animation ? 'animate' : 'attr']({
d: path
}, animation);
} else {
categorized = this.categories && !this.isRadial;
attribs = {
'stroke-width': options.width || (categorized ? this.transA : 1),
stroke: options.color || (categorized ? 'rgba(155,200,255,0.2)' : '#C0C0C0'),
zIndex: options.zIndex || 2
};
if (options.dashStyle) {
attribs.dashstyle = options.dashStyle;
}
this.cross = this.chart.renderer.path(path).attr(attribs).add();
}
}
});
})(Highcharts);
Example: http://jsfiddle.net/o355e82b/5/

Resources