I am using highchart-react-official, I am using it draw two charts:
1) Line chart with multiple series
2) Column Chart.
Now I want, if I hover over a point in first chart, it should display highlight point on both lines of first chart and column chart in second chart. Like a synchronized chart: http://jsfiddle.net/doc_snyder/51zdn0jz/6/
This is my code:
((H) => {
H.Pointer.prototype.reset = () => undefined;
/**
* Highlight a point by showing tooltip, setting hover state and
draw crosshair
*/
H.Point.prototype.highlight = function highlight(event) {
event = this.series.chart.pointer.normalize(event);
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh(this); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(event, this); // Show the crosshair
};
H.syncExtremes = function syncExtremes(e) {
const thisChart = this.chart;
if (e.trigger !== 'syncExtremes') {
// Prevent feedback loop
Highcharts.each(Highcharts.charts, (chart) => {
if (chart && chart !== thisChart) {
if (chart.xAxis[0].setExtremes) {
// It is null while updating
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, {
trigger: 'syncExtremes',
});
}
}
});
}
};
})(Highcharts);
componentDidMount() {
this.changeChart();
['mousemove', 'touchmove', 'touchstart'].forEach((eventType) => {
document
.getElementById('tab__charts')
.addEventListener(eventType, (e) => {
for (let i = 0; i < Highcharts.charts.length; i += 1) {
const chart = Highcharts.charts[i];
// let secSeriesIndex = 1;
if (chart) {
// Find coordinates within the chart
const event = chart.pointer.normalize(e);
// Get the hovered point
chart.series.forEach(series => {
const point = series.searchPoint(event, true);
if (point) {
point.highlight(e);
}
});
}
}
});
});
}
Some imporant chart config:
tooltip: {
enabled: true,
useHTML: true,
shared: true,
}
xAxis: {
events: {
setExtremes: (e) => {
Highcharts.syncExtremes(e);
},
}
}
Now this code is working perfectly by synchronizing tooltip on both charts. But the issue is in first chart, it has two lines, when I hover over first line it hightlights the point with round circle, but the second line is not getting highlighted.
And the reason I found for that is in point.highlight(e); in componendDidMount
For second series, this line giving error on hover:
More specifically, point.highligh(e) is calling top function: H.Point.prototype.highlight = (), check top of the question, in that function this function call is resulting in error
this.series.chart.tooltip.refresh(this);
Note: I'll try to reproduce and create a jsfiddle or something like that, but posting this just so if anyone can help figure this out.
And I am passing Array of Object in Series data, because I need more informative tooltip on chart point:
data: [{
"x": "2018-12-23T11:00:09.311Z",
"y": 107.54,
"data": {
"Toltip Info 1": "1,884,681,725",
"Tooltip info 2": "158,039,757.99",
"price":"107.54"
}
}]
Here is demo of the issue: https://codesandbox.io/s/pk2w85jpk0
The problem is the data format, x values should be in milliseconds:
data: [
{
x: new Date("2018-12-25T09:00:06.247Z").getTime(),
y: 6609592859.48
},
...
]
Live demo: https://codesandbox.io/s/ovk25m493q
Related
I have a couple of charts, some with a single series, others with multiple series. I show shared tooltips for charts with multiple series. I show a vertical crosshair on each chart.
Now I would like to synchronise the charts. Moving the mouse in one chart should show the described features (crosshair and toolip) on the correct place in all charts, with updated data.
As you can see in fiddle, this does not work completely. The x position is synchronised correctly. Tooltips are updated. But mouseover on the second chart shows a tooltip in the first chart with only one y value instead of two. [Edit: I've found a way to update crosshairs; but it does not not work in every chart, when I set
tooltip: {
shared: true,
maybe there is a better solution?]
https://jsfiddle.net/hbaed859/1/
let highlightFunction = function (point) {
Highcharts.charts.forEach(chart => {
chart.series.forEach(s => {
if (point.series.name != s.name) {
s.points.forEach(p => {
if (p.index === point.index) {
p.setState('hover')
p.series.chart.tooltip.refresh(p)
// shows tooltip, but ignores "shared: true"
chart.xAxis[0].drawCrosshair(null, p);
...
chart = Highcharts.chart('chart1', {
series: [{
point: {
events: {
mouseOver(e) {
let point = this;
highlightFunction(point)
}
...
[edit] ... and the method is nice for small datasets, for large one it needs too long to recalculute the crossjair positions for all the charts.
You are really close to achieve the wanted result. Use the hideCrosshair method for crosshair visibility and add points from all chart's series to the tooltip.refresh method call. Optimized code below:
function setHighlightState() {
const point = this;
Highcharts.charts.forEach(chart => {
if (point.series.chart !== chart) {
const matchedPoints = [];
chart.series.forEach(s => {
const matchedPoint = s.points[point.index];
matchedPoints.push(matchedPoint);
matchedPoint.setState('hover');
});
chart.tooltip.refresh(matchedPoints)
chart.xAxis[0].drawCrosshair(null, matchedPoints[0]);
}
});
}
function hideHighlightState() {
const point = this;
Highcharts.charts.forEach(chart => {
if (point.series.chart !== chart) {
chart.series.forEach(s => {
s.points[point.index].setState('');
});
chart.tooltip.hide();
chart.xAxis[0].hideCrosshair();
}
});
}
Live demo: https://jsfiddle.net/BlackLabel/nwj3co0a/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Axis#hideCrosshair
I'm trying to use Highcharts Synchronization for a slightly complex chart. I've gotten synchronization to work just fine on graphs with just 1 series for other plots.
You can check out the example here: http://jsfiddle.net/nhng5827/o9uh2jqy/42/
I've dumbed down my complex chart to just a few points, and usually the chart has 6 lines as opposed to 4. As you can see, the tooltip only appears on the right side of the graph, and everything is delayed. I also do not want the callout box to have any numbers read out if the tooltip is not hovered the specific lines.
I've done a fair bit of research and have implemented some code I've seen that allows for charts with 2 series to be synchronized (http://jsfiddle.net/mjsdnngq/72/), however I think the biggest difference in mine is that the series only go to about half of the actual chart.
$('#container').bind('mousemove touchmove touchstart', function (e) {
var chart,
points,
i,
secSeriesIndex = 1;
for (i = 0; i < Highcharts.charts.length; i++) {
chart = Highcharts.charts[i];
e = chart.pointer.normalize(e); // Find coordinates within the chart
points = [chart.series[0].searchPoint(e, true), chart.series[1].searchPoint(e, true),chart.series[2].searchPoint(e, true),chart.series[3].searchPoint(e, true)]; // Get the hovered point
if (points[0] && points[1]&& points[2]&& points[3]) {
// if (!points[0].series.visible) {
// points.shift();
// secSeriesIndex = 0;
// }
// if (!points[secSeriesIndex].series.visible) {
// points.splice(secSeriesIndex,1);
// }
if (points.length) {
chart.tooltip.refresh(points); // Show the tooltip
chart.xAxis[0].drawCrosshair(e, points[0]); // Show the crosshair
chart.xAxis[0].drawCrosshair(e, points[3]); // Show the crosshair
}
}
}
});
/**
* Override the reset function, we don't need to hide the tooltips and crosshairs.
*/
Highcharts.Pointer.prototype.reset = function () {
return undefined;
};
Highcharts.Point.prototype.highlight = function (event) {
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh([points[0],points[1],points[2],points[3]]); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(event, this); // Show the crosshair
};
/**
* Synchronize zooming through the setExtremes event handler.
*/
function syncExtremes(e) {
var thisChart = this.chart;
if (e.trigger !== 'syncExtremes') { // Prevent feedback loop
Highcharts.each(Highcharts.charts, function (chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, { trigger: 'syncExtremes' });
}
}
});
}
}
Any help would be immensely appreciated.
To correctly synchronize charts, you have to clear points state on every mousemove event:
Highcharts.each(chart.series, function(s) {
Highcharts.each(s.points, function(p) {
p.setState('');
});
});
Live demo: http://jsfiddle.net/BlackLabel/cvb6pwsu/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Point#setState
Also, please take a look at this example: http://jsfiddle.net/BlackLabel/6f37b42q/, it can be useful for you.
I need to create custom legend for my donut chart using ChartJS library.
I have created donut with default legend provided by ChartJS but I need some modification.
I would like to have value above the car name. Also I don't like sticky legend I want to have it separate from donut so I can change the style for fonts, boxes (next to the text "Audi" for example)
I know there is some Legend generator but I'm not sure how to use it with VueJS - because I'm using VueJS as a framework
This is how my legend looks like now - http://imgur.com/a/NPUoi
My code:
From Vue component where I import a donut component:
<div class="col-md-6">
<div class="chart-box">
<p class="chart-title">Cars</p>
<donut-message id="chart-parent"></donut-message>
</div>
</div>
Javascript:
import { Doughnut } from 'vue-chartjs'
export default Doughnut.extend({
ready () {
Chart.defaults.global.tooltips.enabled = false;
Chart.defaults.global.legend.display = false;
this.render({
labels: ['Audi','BMW','Ford','Opel'],
datasets: [
{
label: 'Cars',
backgroundColor: ['#35d89b','#4676ea','#fba545','#e6ebfd'],
data: [40, 30, 20, 10]
}
]
},
{
responsive: true,
cutoutPercentage: 75,
legend: {
display: true,
position: "right",
fullWidth: true,
labels: {
boxWidth: 10,
fontSize: 14
}
},
animation: {
animateScale: true
}
})
}
});
I'm having the same problem trying to understand the documentation and this link might clarify the process of customize the legends:
https://codepen.io/michiel-nuovo/pen/RRaRRv
The trick is to track a callback to build your own HTML structure and return this new structure to ChartJS.
Inside the options object:
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
for (var i = 0; i < chart.data.datasets[0].data.length; i++) {
text.push('<li><span style="background-color:' +
chart.data.datasets[0].backgroundColor[i] + '">');
if (chart.data.labels[i]) {
text.push(chart.data.labels[i]);
}
text.push('</span></li>');
}
text.push('</ul>');
return text.join("");
}
Second, you need a container to insert the new html and using the method myChart.generateLegend() to get the customized html:
$("#your-legend-container").html(myChart.generateLegend());
After that, if you need, track down the events:
$("#your-legend-container").on('click', "li", function() {
myChart.data.datasets[0].data[$(this).index()] += 50;
myChart.update();
console.log('legend: ' + data.datasets[0].data[$(this).index()]);
});
$('#myChart').on('click', function(evt) {
var activePoints = myChart.getElementsAtEvent(evt);
var firstPoint = activePoints[0];
if (firstPoint !== undefined) {
console.log('canvas: ' +
data.datasets[firstPoint._datasetIndex].data[firstPoint._index]);
}
else {
myChart.data.labels.push("New");
myChart.data.datasets[0].data.push(100);
myChart.data.datasets[0].backgroundColor.push("red");
myChart.options.animation.animateRotate = false;
myChart.options.animation.animateScale = false;
myChart.update();
$("#your-legend-container").html(myChart.generateLegend());
}
}
Another solution that I found, if you don't need to change the HTMl structure inside the legend, you can just insert the same HTML in your legend container and customize it by CSS, check this another link:
http://jsfiddle.net/vrwjfg9z/
Hope it works for you.
You can extract the legend markup.
data () {
return {
legendMarkup: ''
}
},
ready () {
this.legendMarkup = this._chart.generateLegend()
}
And in your template you can output it.
<div class="legend" ref="legend" v-html="legendMarkup"></div>
this._chart is the internal chartjs instance in vue-chartjs. So you can call all chartjs methods which are not exposed by vue-chartjs api over it.
However you can also use the legend generator. The usage is the same in vue. You can pass in the options, use callbacks etc.
please check this documentation
.
Legend Configuration
The chart legend displays data about the datasets that area appearing on the chart.
Configuration options
Position of the legend. Options are:
'top'
'left'
'bottom'
'right'
Legend Item Interface
Items passed to the legend onClick function are the ones returned from labels.generateLabels. These items must implement the following interface.
{
// Label that will be displayed
text: String,
// Fill style of the legend box
fillStyle: Color,
// If true, this item represents a hidden dataset. Label will be rendered with a strike-through effect
hidden: Boolean,
// For box border. See https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap
lineCap: String,
// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
lineDash: Array[Number],
// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset
lineDashOffset: Number,
// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
lineJoin: String,
// Width of box border
lineWidth: Number,
// Stroke style of the legend box
strokeStyle: Color
// Point style of the legend box (only used if usePointStyle is true)
pointStyle: String
}
Example
The following example will create a chart with the legend enabled and turn all of the text red in color.
var chart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
legend: {
display: true,
labels: {
fontColor: 'rgb(255, 99, 132)'
}
}
}
});
Custom On Click Actions
It can be common to want to trigger different behaviour when clicking an item in the legend. This can be easily achieved using a callback in the config object.
The default legend click handler is:
function(e, legendItem) {
var index = legendItem.datasetIndex;
var ci = this.chart;
var meta = ci.getDatasetMeta(index);
// See controller.isDatasetVisible comment
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
// We hid a dataset ... rerender the chart
ci.update();
}
Lets say we wanted instead to link the display of the first two datasets. We could change the click handler accordingly.
var defaultLegendClickHandler = Chart.defaults.global.legend.onClick;
var newLegendClickHandler = function (e, legendItem) {
var index = legendItem.datasetIndex;
if (index > 1) {
// Do the original logic
defaultLegendClickHandler(e, legendItem);
} else {
let ci = this.chart;
[ci.getDatasetMeta(0),
ci.getDatasetMeta(1)].forEach(function(meta) {
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
});
ci.update();
}
};
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
legend: {
}
}
});
Now when you click the legend in this chart, the visibility of the first two datasets will be linked together.
HTML Legends
Sometimes you need a very complex legend. In these cases, it makes sense to generate an HTML legend. Charts provide a generateLegend() method on their prototype that returns an HTML string for the legend.
To configure how this legend is generated, you can change the legendCallback config property.
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
legendCallback: function(chart) {
// Return the HTML string here.
}
}
});
I have a c3.js stacked barchart that updates when you hover over a Leaflet map, and I need the tooltip to be static as otherwise the user will hover over other areas of the map and change the data in the barchart, before actually reaching it. However, I'm new to coding and especially new to C3 and I can't get around how to make the tooltip static. Also, does anyone know how to style the tooltip later? I have only found very complex examples online, but it feels like I should be able to do it somewhere after I generate the chart.
Any help would be much appreciated!
Here is my code:
function getMiniChartData(properties) {
var values = [
['rape', rape[properties['gss_code']]],
['other sexual', other_sexual[properties['gss_code']]]];
console.log(values);
return values;
}
var chart;
function drawMiniChart(properties) {
console.log('drawing mini chart');
var data = getMiniChartData(properties);
chart = c3.generate({
bindto: '#minichart',
color: {
pattern: ['#E31A1C', '#BD0026']
},
point: {
show: false
},
tooltip: {
show: true
},
data: {
columns: data,
type: 'bar',
groups: [
['rape', 'other sexual']
]
},
axis: {
y: {
max:60,
min:0
}
},
grid: {
y: {
lines: [{
value: 0
}]
}
}
});
}
function updateMiniChartData(properties) {
console.log('updating mini chart');
var data = getMiniChartData(properties);
chart.load({
columns: data
});
}
Just edit the position in the tooltip :
position: function () {
var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
position.top = 0;
return position;
}
This will set the tooltip to always be at the top of the point. So it stays at the same y coordinate (top:0) but follows the points x value. You could go further and set it to stay at one position on the page.
Check this fiddle I have put together : http://jsfiddle.net/thatOneGuy/owhxgaqm/185/
This question will help you out : C3 charts - contents of tooltip clickable
If you want it visible all the time just add this code :
var originalHideTooltip = chart.internal.hideTooltip
chart.internal.hideTooltip = function () {
setTimeout(originalHideTooltip, 100)
};
Using highcharts, I am adding multiple series to an exist chart. When adding the series I need to be able to set tooltip on or off depending on the series added.
Here is where I am at:
widget.chart.addSeries({
data: newSeries,
color: color,
tooltip: {
enabled: true,
formatter: function() {
return 'test';
}
}
});
Try this.
Inside chart options.
tooltip: {
formatter: function() {
var text = false;
if(this.series.name == "GOOG") {
// just an example of serie name
// you can use index or other data too
text = 'test';
}
return text;
}
}
demo