References in axis using chart.js (or another library) - charts

Im trying to make a graph like this:
https://www.google.com/finance?q=BCBA:PAMP
I have a line chart in chart.js, now I want to add labels (like the letters A, B, C) for certain dates.
Can't find a doc/example to start from. Any idea?
If its more simple to do with another library a recommendation is more than welcome.
Thanks!

Unfortunately, there is no native support in chart.js for what you are wanting. However, you can certainly add this capability using the plugin interface. This requires that you implement your own logic to draw the canvas pixels at the locations that you want them. It might sound challenging, but its easier than it sounds.
Here is an example plugin that will add a value above specific points in the chart (based upon configuration).
Chart.plugins.register({
afterDraw: function(chartInstance) {
if (chartInstance.config.options.showDatapoints || chartInstance.config.options.showDatapoints.display) {
var showOnly = chartInstance.config.options.showDatapoints.showOnly || [];
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize + 5, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = fontColor;
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
if (showOnly.includes(dataset.data[i])) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
ctx.fillText(dataset.data[i], model.x, yPos);
}
}
});
}
}
});
It allows you to configure which points you want to annotate using this new configuration. The showOnly option contains the points that you want to label.
options: {
showDatapoints: {
display: true,
showOnly: [3, 10, 9]
},
}
Obviously, this only adds the datapoint value at the specified points, but you can just change the plugin to paint whatever you want to show instead. Simply replace ctx.fillText(dataset.data[i], model.x, yPos) with different code to render something different on the canvas.
Here is a codepen example to show you want it looks like.

Related

leaftletjs-adding points dynamically and draw line string

I am trying to draw the path of a flight using leafletjs and geojson. I'll be getting the geometry from a stream.
this is what I have done so far:
let index = 0;
let geoJsonLayer;
let intervalFn = setInterval(function () {
let point = trackData.features[index++];
if(point) {
let coords = point.geometry.coordinates;
coords.pop();
coords.reverse();
geoFeature.geometry.coordinates.push(coords);
if(map.hasLayer(geoJsonLayer)) map.removeLayer(geoJsonLayer);
geoJsonLayer = L.geoJson(geoFeature, {
onEachFeature: (feature, layer) => {
const content = feature.properties.title;
layer.bindPopup(content);
}
});
geoJsonLayer.addTo(map);
// console.log(coords);
} else {
clearInterval(intervalFn);
}
}, 100);
setInterval is to simulate the part whereby I get the geometry from a stream.
now when a user clicks on the path I need to show some properties of the path, and I am trying to use the onEachFeature for that, but its not working correctly.
I suspect its because I am removing the layers (I did this to improve the performance)
Is there any other better ways to do what I am trying to achieve ?
You should probably try addLatLng()
Adds a given point to the polyline.
Your geoFeature sounds to be a single Feature, so your geoJsonLayer will contain a single layer (polyline):
let myPolyline;
geoJsonLayer.eachLayer(function (layer) {
myPolyline = layer; // Will be done only once actually.
});
// When you receive a new point…
myPolyline.addLatLng([lat, lng]);
With this you should not have to remove your layers every time.
The popup should therefore stay open, if it is shown.
Demo: https://jsfiddle.net/3v7hd2vx/265/ (click on the button to add new points)

chartJS - points overplotting, jittering, noise?

Hey I'm using chartJS and I have a graph that looks like this:
As you can see the data points overlap each other which makes it harder to read. In RStudio there is a solution for that you simply set position="jittering" and it will add a slight noise around the dots.
Unfortunately, there is no such capability in chart.js today. However, that doesn't mean you can't still get the functionality that you want. There are a couple of options that come to mind.
1) Implement your own jitter function and use it to pre-process your data before passing to chart.js. Here is an example.
var jitter = function(data) {
return data.map(function(e) {
var xJitter = Math.random() * (-1 - 1) + 1;
var yJitter = Math.random() * (-1 - 1) + 1;
return {
x: e.x + xJitter,
y: e.y + yJitter,
}
});
};
Here is a codepen example showing a side by side example of charts with original and jittered data.
2) Use the jitter function above and add it as a chart.js plugin such that you can enable/disable it automatically in any given chart. Here is an example (note that we are using the jitter function above.
Chart.plugins.register({
afterInit: function(chartInstance) {
if (chartInstance.config.options.jitter) {
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
chartInstance.data.datasets.forEach(function (dataset) {
dataset.data = jitter(dataset.data);
});
}
}
});
Then just add this into your charts options config.
options: {
jitter: true,
}
Here is a codepen example showing a side by side example of charts with original and jittered data using the plugin.
Obviously, you would want to implement a much more robust jitter function that accepts some sort of jitter options so that it can be intelligently applied to any type of dataset. I'll leave that for you to implement if you decide to take this approach.

Chartjs: Is it possible to add phases in line chart?

Hello I want to create a dynamic chart that looks like this: phases in line chart
Is it possible with ChartJs to create vertical lines at certain milestones? (If possible also give each phase another color)
If not, what other libraries can handle dynamic line chart libraries that support phases?
Using Chart.js plugins can help you with this. They let you handle specific events that are triggered during the chart creation like beforeInit, afterUpdate or afterDraw and are also easy to implement :
var myPlugin = {
afterDraw: function(chart) {
// This will be triggered after the chart is drawn
}
};
Chart.pluginService.register(myPlugin);
A plugin doing what you are looking for will be linked below, but first let me explain how it works.
In your chart options, you'll need to add a new property, called phases, which is an array :
// The following is an example that won't work with every chart
options: {
scales: {
xAxes: [{
// `from` & `to` must exist in your xAxe ticks
phases: [{
from: "January",
to: "March",
// You can also define the color here
color: "blue"
},
{
from: "May",
to: "June",
color: "red"
}]
}]
}
}
And then, here is the plugin that use this information :
var phasePlugin = {
// You need to handle the `afterDraw` event since you draw phases edges
// after everything is drawn
afterDraw: function(chart)
{
// All the variables the plugin needs follow ...
var ctx = chart.chart.ctx;
var xAxeId = chart.config.options.scales.xAxes[0].id;
var xAxe = chart.scales[xAxeId];
var yAxeId = chart.config.options.scales.yAxes[0].id;
var yAxe = chart.scales[yAxeId];
var ticks = xAxe.ticks;
var phases = chart.config.options.scales.xAxes[0].phases;
// For every phase ...
for (var i = 0; i < phases.length; i++) {
// We check if the tick actually exists
if (ticks.indexOf(phases[i].from) == -1 || (ticks.indexOf(phases[i].to) == -1)) {
// Else, we skip to the next phase
continue;
}
// We get the x position of the first limit tick
var xPos = ((xAxe.width - xAxe.paddingRight) / xAxe.maxIndex) * ticks.indexOf(phases[i].from) + xAxe.left + 1;
// And draw the dashed line
ctx.setLineDash([8, 8]);
ctx.strokeStyle = phases[i].color;
ctx.strokeRect(xPos, yAxe.top, 0, yAxe.height);
// Same for the other limit
xPos = ((xAxe.width - xAxe.paddingRight) / xAxe.maxIndex) * ticks.indexOf(phases[i].to) + xAxe.left + 1;
ctx.strokeStyle = phases[i].color;
ctx.strokeRect(xPos, yAxe.top, 0, yAxe.height);
ctx.setLineDash([1,0]);
}
}
};
You can see a fully working example in this jsFiddle and here is its result :

Display values outside of pie chart in chartjs

When I hover on pie chart, the values are displayed in tooltip. However, I want to display values outside of pie chart. I want to make chart like this image:
How to do this?
I was able to get something similar working using chart.js v2.3.0 using both the plugin API and extending chart types API. You should be able to take this as a starting point and tweak it to your needs.
Here is how it looks after being rendered.
Note, this requires digging deep into chart.js internals and could break if they change the way tooltips are positioned or rendered in the future. I also added a new configuration option called showAllTooltips to enable selectively using the plugin on certain charts. This should work for all chart types, but I am currently only using it for pie, doughnut, bar, and line charts so far.
With that said, here is a working solution for the image above.
Chart.plugins.register({
beforeRender: function (chart) {
if (chart.config.options.showAllTooltips) {
// create a namespace to persist plugin state (which unfortunately we have to do)
if (!chart.showAllTooltipsPlugin) {
chart.showAllTooltipsPlugin = {};
}
// turn off normal tooltips in case it was also enabled (which is the global default)
chart.options.tooltips.enabled = false;
// we can't use the chart tooltip because there is only one tooltip per chart which gets
// re-positioned via animation steps.....so let's create a place to hold our tooltips
chart.showAllTooltipsPlugin.tooltipsCollection = [];
// create a tooltip for each plot on the chart
chart.config.data.datasets.forEach(function (dataset, i) {
chart.getDatasetMeta(i).data.forEach(function (sector, j) {
// but only create one for pie and doughnut charts if the plot is large enough to even see
if (!_.contains(['doughnut', 'pie'], sector._chart.config.type) || sector._model.circumference > 0.1) {
var tooltip;
// create a new tooltip based upon configuration
if (chart.config.options.showAllTooltips.extendOut) {
// this tooltip reverses the location of the carets from the default
tooltip = new Chart.TooltipReversed({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
} else {
tooltip = new Chart.Tooltip({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
}
// might as well initialize this now...it would be a waste to do it once we are looping over our tooltips
tooltip.initialize();
// save the tooltips so they can be rendered later
chart.showAllTooltipsPlugin.tooltipsCollection.push(tooltip);
}
});
});
}
},
afterDraw: function (chart, easing) {
if (chart.config.options.showAllTooltips) {
// we want to wait until everything on the chart has been rendered before showing the
// tooltips for the first time...otherwise it looks weird
if (!chart.showAllTooltipsPlugin.initialRenderComplete) {
// still animating until easing === 1
if (easing !== 1) {
return;
}
// animation is complete, let's remember that fact
chart.showAllTooltipsPlugin.initialRenderComplete = true;
}
// at this point the chart has been fully rendered for the first time so start rendering tooltips
Chart.helpers.each(chart.showAllTooltipsPlugin.tooltipsCollection, function (tooltip) {
// create a namespace to persist plugin state within this tooltip (which unfortunately we have to do)
if (!tooltip.showAllTooltipsPlugin) {
tooltip.showAllTooltipsPlugin = {};
}
// re-enable this tooltip otherise it won't be drawn (remember we disabled all tooltips in beforeRender)
tooltip._options.enabled = true;
// perform standard tooltip setup (which determines it's alignment and x, y coordinates)
tooltip.update(); // determines alignment/position and stores in _view
tooltip.pivot(); // we don't actually need this since we are not animating tooltips, but let's be consistent
tooltip.transition(easing).draw(); // render and animate the tooltip
// disable this tooltip in case something else tries to do something with it later
tooltip._options.enabled = false;
});
}
},
});
// A 'reversed' tooltip places the caret on the opposite side from the current default.
// In order to do this we just need to change the 'alignment' logic
Chart.TooltipReversed = Chart.Tooltip.extend({
// Note: tooltipSize is the size of the box (not including the caret)
determineAlignment: function(tooltipSize) {
var me = this;
var model = me._model;
var chart = me._chart;
var chartArea = me._chartInstance.chartArea;
// set caret position to top or bottom if tooltip y position will extend outsite the chart top/bottom
if (model.y < tooltipSize.height) {
model.yAlign = 'top';
} else if (model.y > (chart.height - tooltipSize.height)) {
model.yAlign = 'bottom';
}
var leftAlign, rightAlign; // functions to determine left, right alignment
var overflowLeft, overflowRight; // functions to determine if left/right alignment causes tooltip to go outside chart
var yAlign; // function to get the y alignment if the tooltip goes outside of the left or right edges
var midX = (chartArea.left + chartArea.right) / 2;
var midY = (chartArea.top + chartArea.bottom) / 2;
if (model.yAlign === 'center') {
leftAlign = function(x) {
return x >= midX;
};
rightAlign = function(x) {
return x < midX;
};
} else {
leftAlign = function(x) {
return x <= (tooltipSize.width / 2);
};
rightAlign = function(x) {
return x >= (chart.width - (tooltipSize.width / 2));
};
}
overflowLeft = function(x) {
return x - tooltipSize.width < 0;
};
overflowRight = function(x) {
return x + tooltipSize.width > chart.width;
};
yAlign = function(y) {
return y <= midY ? 'bottom' : 'top';
};
if (leftAlign(model.x)) {
model.xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
if (overflowLeft(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
} else if (rightAlign(model.x)) {
model.xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
if (overflowRight(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
}
}
});

sencha touch n3dv charts

I've added a nvd3 chart to my sencha touch app.
Apparently though the size of the box where the chart will be inserted is undefined at the time the chart is created. This turns out in a graph with standard dimensions (960x350 approx), way too large!
How can I modify the widht and height of the chart? The visual error I get is that the chart has a larger width, the component containing it are smaller and the chart is not completely
visible (it's like it misses a resize effect to adapt its size to the containing box).
My code, which is inside a sencha component goes like this:
nv.addGraph(Ext.bind(function(){
var chart = nv.models.discreteBarChart()
.x(function(d) { return d.label; })
.y(function(d) { return d.value; })
.staggerLabels(true)
.tooltips(false)
.showValues(true);
var w = 550;
var h = 280;
var svg = d3.select(this.innerElement.dom).append('svg');
// setting axis property doesn't work:
var x = d3.scale.ordinal()
.domain(d3.range(10))
.rangeRoundBands([0, w], 1);
chart.xAxis
.tickFormat(d3.format(',f'));
chart.xAxis.scale(x);
chart.yAxis
.tickFormat(d3.format(',f'));
//setting svg properties doesn't work:
svg.attr("width", w)
.attr("height", h);
svg.datum(this.getChartData()).transition().duration(500).call(chart);
//if I comment this, nothing changes, what is this method for?
nv.utils.windowResize(chart.update);