Chart.js v2 overwrite draw function - javascript

I'm using chart.js 2.6.0, which is the latest version.
I'm trying to overwrite the bar charts in order to make the corners rounded. I found this script: https://github.com/jedtrow/Chart.js-Rounded-Bar-Charts/blob/master/Chart.extentions.js and it works good.
I created a new graph option: "is_curved" because I have multiple graphs on the same page and I want to make rounded corners only for some of them.
The problem in my case is that if I'm using the following code, all the charts have rounded corners:
Chart.elements.Rectangle.prototype.draw = function() {
//code to make corners rounded
}
The function:
Chart.elements.Rectangle.prototype.draw
Is called on all the charts. I want it to be executed only when the option "is_curved" is set to true. So I tried:
Chart.elements.Rectangle.prototype.draw = function() {
if(!this._chart.options.is_curved) {
return;
}
//code to make corners rounded
}
But this is still wrong because the graphs that doesn't have the "is_curved" option set to true are empty, I guess that the fact that I call the .draw method removes all the existing functionality, even if I don't add new functionality for them.
Any idea how can I set the draw function to work only for the charts with "is_curved" option set to true? Thanks.

change this line of code:
var cornerRadius = 5;
to this :
var cornerRadius = this._chart.options.is_curved ? 5 : 0;
this will set barĀ­'s corner radius to 5 , if the is_curved property is set to true for any particular chart, otherwise will set to 0 .
see - working example on JSFiddle

Related

Angular-Chart-JS - Line chart with different fill colors according to points' range

I'm using Angular-Chart-js for my website to display some types of graphs, one of them is a line chart.
I would like the line chart to be color-filled, but with different colors according to the y-axis' value. Like in this photo:
I've tried to have different data arrays in the "data" array of the graph, the first one has all the values, the second one has all but the ones painted in green (on the right), the third is the same array only until the purple range etc. and then have for each dataset its own color, but eventually I get a graph with a single color according to the last dataset color.
What am I missing? Is there any way to accomplish that?
Thanks.
Unfortunately, you cannot achieve this with the current chart.js configuration options. The reason is because the line chart backgroundColor option (the option that controls the color of the line chart fill) only accepts a single value.
After digging through the current chart.js 2.5 source, I found that it is possible to extend the line element's draw() method and force chart.js to use a canvas linear gradient for the fill (instead of just a single color). With a little bit of math, we can convert the x position of each point into a linear gradient color stop position and build a gradient.
With this enhancement, you can now pass in an array of colors to the line chart backgroundColor option to achieve varying colored fill regions. Here is an example of what a resulting chart would look like.
Here is how to actually do it (with a working example at the bottom)
First, we must extend Chart.elements.Line and overwrite it's draw() method so that we can build the linear gradient based upon the position of each point, use it as the line fill, and then draw the line.
// save the original line element so we can still call it's
// draw method after we build the linear gradient
var origLineElement = Chart.elements.Line;
// define a new line draw method so that we can build a linear gradient
// based on the position of each point
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var vm = this._view;
var backgroundColors = this._chart.controller.data.datasets[this._datasetIndex].backgroundColor;
var points = this._children;
var ctx = this._chart.ctx;
var minX = points[0]._model.x;
var maxX = points[points.length - 1]._model.x;
var linearGradient = ctx.createLinearGradient(minX, 0, maxX, 0);
// iterate over each point to build the gradient
points.forEach(function(point, i) {
// `addColorStop` expects a number between 0 and 1, so we
// have to normalize the x position of each point between 0 and 1
// and round to make sure the positioning isn't too percise
// (otherwise it won't line up with the point position)
var colorStopPosition = roundNumber((point._model.x - minX) / (maxX - minX), 2);
// special case for the first color stop
if (i === 0) {
linearGradient.addColorStop(0, backgroundColors[i]);
} else {
// only add a color stop if the color is different
if (backgroundColors[i] !== backgroundColors[i-1]) {
// add a color stop for the prev color and for the new color at the same location
// this gives a solid color gradient instead of a gradient that fades to the next color
linearGradient.addColorStop(colorStopPosition, backgroundColors[i - 1]);
linearGradient.addColorStop(colorStopPosition, backgroundColors[i]);
}
}
});
// save the linear gradient in background color property
// since this is what is used for ctx.fillStyle when the fill is rendered
vm.backgroundColor = linearGradient;
// now draw the lines (using the original draw method)
origLineElement.prototype.draw.apply(this);
}
});
Then, we have to also extend the line chart to ensure that the line element used by the chart is the one that we extended above (since this property is already set at load time)
// we have to overwrite the datasetElementType property in the line controller
// because it is set before we can extend the line element (this ensures that
// the line element used by the chart is the one that we extended above)
Chart.controllers.line = Chart.controllers.line.extend({
datasetElementType: Chart.elements.Line,
});
With this done, we can now pass in an array of colors to the line chart backgroundColor property (instead of just a single value) to control the line fill.
Here is a codepen example that demonstrates all that has been discussed.
Caveats:
This approach could break in future chart.js releases since we are messing with the internals.
I'm not familiar with angular-chart.js, so I cannot provide insight on how to integrate the above chart.js changes into the angular directive.
If you would like to have this capability with angular2 and ng2-charts there maybe a less "hacked" way to do this but this is how I was able to apply Jordan's code to make it work:
Downgrade ng2-chart's dependency on Chart.js from 2.7.x to 2.5.
- From your project's directory: npm install chart.js#2.5 --save
Inside node_modules/chart.js/src/charts:
- Add Jordan's code to Chart.line.js( inside the export ) after the Chart.line function
Rebuild Chart.js/dist:
- run npm install
run gulp build
If you get an error from socket.io code, then you will need to upgrade those dependencies to a more current version of socket.io, I believe Karma might have an old version of socket.io that you could upgrade to 2.0.
Anyway this worked for me. It is not fully tested and it is definitely a "hack" but I did not want to learn Chart.js 2.7 to figure out why Jordan's code would not work with it. Which is definitely the more "proper" way to do it. I suppose it should be integrated as a "plugin".
i decided to do the chartJS 2.5 approach but use the extension above vs modifying the chartjs code itself..
i have to work on some performance optimization, as my charts have over 4000 values.
getting the color array built with the right values (sparse alternate color, maybe for 200 in 4000 values) and then having the extension read it to build the linear gradient is very time consuming. buries the raspberry PI I am using for the chart display system.
I finally decided that to reduce the processing time, I needed to eliminate any extra processing of the list of points.. mine collect, mine creating the color array , and chart building the linear grandient...
so, now I create the linearGradient edges as I go thru the data (all in one pass).. the gradient is an array of structures, that have offset from start of data array, and the color to be applied to that edge, basically does what the original extension does.. so, reduce 800 points to 40 edges. or 800 points to 1 edge (start)...
so, here is my updated extend function.. my app has charts with all three color types,. single fixed, array of colors and the array of edges.. all the other routines above are unchanged
// save the original line element so we can still call it's
// draw method after we build the linear gradient
var origLineElement = Chart.elements.Line;
// define a new line draw method so that we can build a linear gradient
// based on the position of each point
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var vm = this._view;
var backgroundColors = this._chart.controller.data.datasets[this._datasetIndex].backgroundColor;
var points = this._children;
var ctx = this._chart.ctx;
var minX = points[0]._model.x;
var maxX = points[points.length - 1]._model.x;
var linearGradient = ctx.createLinearGradient(minX, 0, maxX, 0);
// if not a single color
if( typeof backgroundColors != 'string' ){
// but is array of colors
if( typeof backgroundColors[0] === 'string' ) {
// iterate over each point to build the gradient
points.forEach(function(point, i) { // start original code
// `addColorStop` expects a number between 0 and 1, so we
// have to normalize the x position of each point between 0 and 1
// and round to make sure the positioning isn't too percise
// (otherwise it won't line up with the point position)
var colorStopPosition = self.roundNumber((point._model.x - minX) / (maxX - minX), 2);
// special case for the first color stop
if (i === 0) {
linearGradient.addColorStop(0, backgroundColors[i]);
} else {
// only add a color stop if the color is different
if ( backgroundColors[i] !== backgroundColors[i-1]) {
// add a color stop for the prev color and for the new color at the same location
// this gives a solid color gradient instead of a gradient that fades to the next color
linearGradient.addColorStop(colorStopPosition, backgroundColors[i - 1]);
linearGradient.addColorStop(colorStopPosition, backgroundColors[i]);
}
}
}); // end original code
} // end of if for color array
// must be a gradient fence position list
else {
// loop thru the fence positions
backgroundColors.forEach(function(fencePosition){
var colorStopPosition = self.roundNumber(fencePosition.offset / points.length, 2);
linearGradient.addColorStop(colorStopPosition,fencePosition.color)
});
} // end of block for handling color space edges
// save the linear gradient in background color property
// since this is what is used for ctx.fillStyle when the fill is rendered
vm.backgroundColor = linearGradient;
} // end of if for just one color
// now draw the lines (using the original draw method)
origLineElement.prototype.draw.apply(this);
}

Chart.js set active segment on initialize

I'm working with chart.js v2 and I am trying simulate the hover state of a segment on a doughnut graph when the chart loads so it appears there is a section alright highlighted.
I've been searching and combing the code for a day now and can't figure out a good way to do this.
Thanks in advance!
Setting a segment's hover style is a bit confusing because its not really documented anywhere. Nevertheless, I was able to figure it out a while back when I wanted to highlight a segment when it's legend label was hovered.
To do this, you need to use the pie chart .updateHoverStyle() prototype method and pass in the segment you want highlighted. The chart segments are stored in the _meta object in an array where each segment index matches the position of each value in your chart's data array.
Here is an example (assuming your chart instance is stored in a var called myPie.
// get the segment we want to highlight
var activeSegment = myPie.data.datasets[0]._meta[0].data[segmentIndexToHihlight];
// update the hover style
myPie.updateHoverStyle([activeSegment], null, true);
// render so we can see it
myPie.render();
You just need to define what segment you want to highlight and store it in a var called segmentIndexToHihlight and it should work.
Here is a codepen example demonstrating this. Note, I purposely did not highlight the segment on load (I wait 3 seconds) so that you can see the change occur.
I found another way to preselect a segment, basically you can simulate a click event on the point. You can find in the dataset model the position x and y. Here you can find my solution:
function simulateClick(x, y) {
const clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 0, 0, 0, x, y,
false, false, false, false, 0, null);
document.elementFromPoint(x, y).dispatchEvent(clickEvent);
}
function initActivePoint(index) {
const activePoint = myChart.data.datasets[0]._meta[0].data[index];
simulateClick(activePoint._model.x, activePoint._model.y);
}
initActivePoint(0);

Highstock gapsize is causing line rendering issue

I'm using Highstock (v4.2.3) to present data in a StockChart with a number of different Y axes, all plotted against time on the X axis. The data has gaps in it, and I'd like to depict those gaps, but when I turn on gapSize (with any value other than zero), there's a weird quirk that causes line rendering issues--when using the navigator to zoom in on certain date ranges (not all), in some cases (whose pattern I've yet to discern) the chart fails to fully render the line across the entire x axis.
This annotated screenshot depicts the issue.
When I turn gapSize off (or explicitly set it to zero), this problem goes away. Note that the gaps themselves appear correctly on the chart (when navigating to a date range that doesn't present the line rendering issue).
plotOptions: {
series: {gapSize:2}
}
Any ideas?
jsFiddle with your issue:
http://jsfiddle.net/2N52H/109/
As you can read in our API:
http://api.highcharts.com/highstock#plotOptions.line.gapSize
A gap size of 5 means that if the distance between two points is
greater than five times that of the two closest points, the graph will
be broken
As far as I know data you have has random gaps so you will never know what is the distance between two closest points. For example if you will have data in every one hour, distance between two closest points will be 15 minutes and your gapSize will be set to 2, you will see only your closest points.
When you are using zoom sometimes your visible data closest distance is changing so the gaps are changing as well.
See this example:
http://jsfiddle.net/2N52H/111/
Maybe you can use xAxis.ordinal parameter to visualise your gaps:
http://api.highcharts.com/highstock#xAxis.ordinal
You can also change standard functionallity by using wrapper. Here you can read about it:
http://www.highcharts.com/docs/extending-highcharts/extending-highcharts
For example you can change gappedPath function:
(function(H) {
H.wrap(H.Series.prototype, 'gappedPath', function(proceed) {
var gapSize = this.options.gapSize,
xAxis = this.xAxis,
points = this.points.slice(),
i = points.length - 1;
if (gapSize && i > 0) { // #5008
// extension for ordinal breaks
while (i--) {
if (points[i + 1].x - points[i].x > gapSize) {
points.splice( // insert after this one
i + 1,
0, {
isNull: true
}
);
}
}
}
return this.getGraphPath(points);
})
}(Highcharts))
example:
http://jsfiddle.net/2N52H/113/
Kind regards.

Dygraph: How to hide x,y value in legend?

While generating a track with the help of dygraph library, I have set the legend: always (this display the legend with series name present on that particular track even without mouseover). But when I move to certain graph, the legend changes and shows the value of each series at that particular mouse pointer. I don't want the legend to change and it should just show the series name and not the values. I am using javascript to implement my app. Any way to do the same?
One trick that might be good enough is to supply a custom valueFormatter function for each axis which simply returns an empty string:
axes: {x: {valueFormatter: function (x) {return ''}},
y: {valueFormatter: function (y) {return ''}},
y2: {valueFormatter: function (y2) {return ''}}
}
This prevents the values from being shown, but still switches between line samples and ':' characters depending on the position of the mouse.
At the moment there's no easy way to do this. Your best bets are to either generate the legend yourself using dygraphs' API or to throw a transparent div over your chart which captures all the mouse events.
Well! it might be not a good way but I have found an alternative to do it. Declared a new boolean variable in dygraph.js which checks whether user wish to see x-y values on legend. Using this variable in function generateLegendHTML (defined in legend.js), i have made the control to always go in the first if loop. This makes the dygraph to always show static legend.
You can do the following -
Step 0 - Add a few necessary options and tags -
labelsDiv: document.getElementById("labels"),// in the dygraph options
<div id="labels"></div> <!-- Add this somewhere in your HTML-->
Step 1 - Declare a variable -
var defaultLabelHTML = undefined;
Step 2 - Use drawCallback option -
drawCallback: function(dygraph, is_initial){
if (is_initial)
{
defaultLabelHTML = document.getElementById("labels").innerHTML;
}
},
Step 3 - Use highlightCallback option -
function(e, x, pts, row)
{
document.getElementById("labels").innerHTML = defaultLabelHTML;
}

Flot curved lines/Spline plugin which works with FillBetween plugin?

The Flot FillBetween plugin works nicely with line charts. However I need to smooth out the lines and make them more curvy. I have seen the CurvedLined plugin and the Spline plugin but both do not work properly with the fillbetween plugin.
Is there any way to use a two curved line/Spline series and fill the area between them? Something like the image below. Also fills any enclosed area between the two series any time when one crosses the other.
I am unfamiliar with the FillBetween plug-in. I am going to focus on aswering the smoothing part.
I had a similar problem where those smoothing options did not work for me either. I used an external plug-in to make the smoothing. It's name is smooth.js and it worked for me.
Smooth.js recives the data array and returns a function. To get a "smoothed point", apply the function to any value between 0 and the length of the array. The idea is to obtain more points than the original dataset.
For example, to smooth an array of values named test:
//obtaining smoothing function
var s = Smooth(test, {
method: Smooth.METHOD_CUBIC,
});
//obtaining smoothed data
//asking for 10 "smoothed points" per each point in the original dataset
test_smoothed = [];
for (var i = 0; i <= test.length; i = i + .1) {
test_smoothed.push(s(i));
}
I made a JSFiddle with this example.
You can use this plug-in and pass the smoothed data to flot and then use the FillBetween.

Categories