trying to extend a chart, so that i can draw lines up to the data point, but this is happening before the default animation. it would look smoother if it applied after.
i have got most of it to work.. but how do i get this to apply after chart animation.
var originalLineDraw = Chart.controllers.line.prototype.draw;
Chart.helpers.extend(Chart.controllers.line.prototype, {
draw: function () {
originalLineDraw.apply(this, arguments);
var chart = this.chart;
var ctx = chart.chart.ctx;
var index = chart.config.data.lineAtIndex;
if (index) {
var xaxis = chart.scales['x-axis-0'];
var yaxis = chart.scales['y-axis-1']
var points = this.chart.getDatasetMeta(this.index).data;
for (var i = 0; i < points.length; i++) {
// var point_x = points[i]._model.x;
var point_y = points[i]._model.y;
ctx.beginPath();
ctx.setLineDash([5, 5]);/*dashes are 5px and spaces are 3px*/
ctx.moveTo(xaxis.getPixelForValue(undefined, i), point_y);
ctx.strokeStyle = '#fff';
ctx.lineTo(xaxis.getPixelForValue(undefined, i), yaxis.bottom);
ctx.stroke();
}
}
}
});
Update...
altho the darw is incorrect it still is playing after animation
var verticalLinePlugin = {
renderVerticalLine: function (chartInstance) {
var chart = chartInstance;
var ctx = chart.chart.ctx;
var maxpoint = [];
//loop the datasets
for (var y = 0; y < chart.config.data.datasets.length; y++) {
var dataset = chart.config.data.datasets[y];
if (dataset.hidden)
continue;
var points = chart.getDatasetMeta(y).data;
for (var i = 0; i < points.length; i++) {
var point_y = points[i]._model.y;
if (point_y < 0)
continue;
var point = maxpoint[i];
if (point == undefined) {
maxpoint.push({ id: i, y: point_y });
} else {
if (point.y > point_y) {
point.y = point_y;
}
}
}
}
var xaxis = chart.scales['x-axis-0'];
var yaxis = chart.scales['y-axis-1']
chart.data.datasets.forEach(function (dataset, i) {
var ds = dataset;
var meta = chart.getDatasetMeta(i);
meta.data.forEach(function (element, index) {
var value = maxpoint[i];
ctx.beginPath();
ctx.setLineDash([5, 5]);
ctx.moveTo(xaxis.getPixelForValue(undefined, i), value.y);
ctx.strokeStyle = '#fff';
ctx.lineTo(xaxis.getPixelForValue(undefined, i), yaxis.bottom);
ctx.stroke();
});
});
},
afterRender: function (chart) {
this.renderVerticalLine(chart);
}
};
Chart.plugins.register(verticalLinePlugin);
I made some small changes (non-intuitive!), and the vertical lines now appear after the animation.
Get the x values from the metadata instead of the data.
Either:
var x_point = element._model.x;
or:
var position = element.tooltipPosition();
var x_point = position.x;
Wrap the drawing in if(!hidden){}, then the vertical lines will disapear and reappear with the data. (The ternary assignment fixes a clash if the data starts hidden)
Do you need the value=max[i]? If just drawing the line up to the points, can get the y_point the same as for x.
var xaxis = chart.scales['x-axis-0'];
var yaxis = chart.scales['y-axis-1'];
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.getDatasetMeta(i);
var hidden = (meta.hidden != undefined) ? meta.hidden : dataset.hidden
if(!hidden){
meta.data.forEach(function (element, index) {
//var value = maxpoint[i];
var x_point = element._model.x;
var y_point = element._model.y;
ctx.beginPath();
ctx.save();
ctx.setLineDash([5, 5])
ctx.strokeStyle = '#fff';
ctx.moveTo(x_point, y_point); // or value.y
ctx.lineTo(x_point, yaxis.bottom)
ctx.stroke();
ctx.restore();
});
}
});
Related
I have a mixed chart of bar and line graphs. Both are showing their data labels but I only want the bar graph to show its own data labels and not the line graph.
I've got this code from How to show data values or index labels in ChartJs (Latest Version)
How should I tweak it?
animation: {
onComplete: function() {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
this.data.datasets.forEach(function (dataset)
{
for( var i = 0; i < dataset.data.length; i++ )
{
for(var key in dataset._meta)
{
var model = dataset._meta[key].data[i]._model;
ctx.fillText(dataset.data[i], model.x, model.y+15);
}
}
});
}
}
I was able to generate pie chart successfully. However, when I was trying to add a select event listener to the pie chart, it is not triggering the function at all.
function handlePieChartResponse(response)
{
if (response.isError()) {
alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var dataTable = response.getDataTable();
if (dataTable.getNumberOfRows() <= 0) {
document.getElementById('dummyTableRow').innerHTML = '<span>No data found</span>';
return;
}
var chartOptions = DEFAULT_PIE_CHART_OPTIONS;
//var chartOverallPmmLevelCalculated = new google.visualization.ChartWrapper({
// 'chartType': 'PieChart',
// 'containerId': 'chartOverallPmmLevelCalculatedHtml',
// options: chartOptions
//});
var chartRecentPmmLevelCalculated = new google.visualization.PieChart(document.getElementById('chartOverallPmmLevelCalculatedHtml'));
chartRecentPmmLevelCalculated.draw(dataTable, chartOptions);
google.visualization.events.addListener(chartRecentPmmLevelCalculated, 'ready', function () { drawPieChart(); });
google.visualization.events.addListener(chartRecentPmmLevelCalculated, 'select', function () { selectHandler(); });
function drawPieChart() {
var responseDataTable = response.getDataTable();
var chartDataTable = new google.visualization.DataTable();
chartDataTable.addColumn('string', 'LEVEL');
chartDataTable.addColumn('number', 'SCORE');
var chartDataTableRow = new Array();
var rowCounter;
var levelValue;
for (rowCounter = 0; rowCounter < responseDataTable.getNumberOfRows() ; rowCounter++) {
var seek = 0 * 1;
levelValue = responseDataTable.getValue(rowCounter, 0);
chartDataTableRow[seek++] = "LEVEL " + levelValue;
chartDataTableRow[seek++] = responseDataTable.getValue(rowCounter, 1);
chartDataTable.addRow(chartDataTableRow);
}
chartDataTable.sort([{ column: 1 }]);
chartOverallPmmLevelCalculated.setDataTable(chartDataTable);
chartOverallPmmLevelCalculated.draw();
}
handlePieChartResponse.drawPieChart = drawPieChart;
}
function selectHandler() {
alert("This alert triggered from pie chart");
var selectedItem = chartRecentPmmLevelCalculated.getSelection();
if (selectedItem) {
var levelSelected = chartOverallPmmLevelCalculated.getValue(selectedItem.row, 0);
alert(levelSelected);
}
}
I attached 2 images that include before and after the click. I was expecting alert message once select on pie chart slice. But no alert is present and the function call is not triggered.
problem has to do with scope
selectHandler is outside of the function handlePieChartResponse
so it can't be found
just move it inside handlePieChartResponse
then set the event, like so...
...addListener(chartRecentPmmLevelCalculated, 'select', selectHandler);
Can you help me how to extend Chart.js v2.0. I need to draw some horizontal lines in the charts, something similar to: http://jsfiddle.net/vsh6tcfd/3/
var originalLineDraw = Chart.controllers.bar.prototype.draw;
Chart.helpers.extend(Chart.controllers.bar.prototype, {
draw: function() {
originalLineDraw.apply(this, arguments);
var chart = this.chart;
var ctx = chart.chart.ctx;
var index = chart.config.data.lineAtIndex;
if (index) {
var xaxis = chart.scales['x-axis-0'];
var yaxis = chart.scales['y-axis-0'];
ctx.save();
ctx.beginPath();
ctx.moveTo(xaxis.getPixelForValue(undefined, index), yaxis.left);
ctx.strokeStyle = '#ff0000';
ctx.lineTo(xaxis.getPixelForValue(undefined, index), yaxis.right);
ctx.stroke();
ctx.restore();
}
}
});
var config = {
type: type,
data: jQuery.extend(true, {}, data),
options: this.chartdata.options,
lineAtIndex: 2
};
new Chart(ctx, config);
Options
With chart.js you have 2 options.
You could create a mix chart types (Example Here). This would allow you to add a line charts to create your lines.
You could create a plugin (See Example Below).
Option 2 would be the one I recommend as it allows you to have more control over the appearance of the lines.
The Fix
demo of the plugin
Chart.js now supports plugins. This allows you to add any features you want to your charts!
To create a plugin you will need to run code after an event has occurred and modify the chart/canvas as needed.
The following code should give you a good starting point:
var horizonalLinePlugin = {
afterDraw: function(chartInstance) {
var yValue;
var yScale = chartInstance.scales["y-axis-0"];
var canvas = chartInstance.chart;
var ctx = canvas.ctx;
var index;
var line;
var style;
if (chartInstance.options.horizontalLine) {
for (index = 0; index < chartInstance.options.horizontalLine.length; index++) {
line = chartInstance.options.horizontalLine[index];
if (!line.style) {
style = "rgba(169,169,169, .6)";
} else {
style = line.style;
}
if (line.y) {
yValue = yScale.getPixelForValue(line.y);
} else {
yValue = 0;
}
ctx.lineWidth = 3;
if (yValue) {
ctx.beginPath();
ctx.moveTo(0, yValue);
ctx.lineTo(canvas.width, yValue);
ctx.strokeStyle = style;
ctx.stroke();
}
if (line.text) {
ctx.fillStyle = style;
ctx.fillText(line.text, 0, yValue + ctx.lineWidth);
}
}
return;
}
}
};
Chart.pluginService.register(horizonalLinePlugin);
If I have some bodies that are repulsed and also have a distance. Right now they bounce back and forth endlessly. Is there a way to add a resistance to the physics engine so that they may come to rest?
var context = Engine.createContext();
var contextSize = context.getSize();
var handler = new EventHandler();
var physicsEngine = new PhysicsEngine();
function addBall(color, i) {
var ball = new Surface ({
size: [50,50],
properties: {
backgroundColor: color,
borderRadius: '100px'
}
});
ball.state = new StateModifier({origin:[0.5,0.5]});
ball.particle = new Circle({radius:50, mass: 20 + i * 1});
return ball;
}
var leftWall = new Wall({normal : [1,0,0], distance : contextSize[0]/2.0, restitution : 0.7});
var rightWall = new Wall({normal : [-1,0,0], distance : contextSize[0]/2.0, restitution : 0.7});
var topWall = new Wall({normal : [0,1,0], distance : contextSize[1]/2.0, restitution : 0.7});
var bottomWall = new Wall({normal : [0,-1,0], distance : contextSize[1]/2.0, restitution : 0.7});
var walls = [leftWall,rightWall,bottomWall,topWall];
var bodies = [];
_.each(['yellow', 'blue','green', 'red', 'orange', 'purple','gray', 'black'],function(color, index) {
var body = addBall(color, index);
bodies.push(body);
});
_.each(bodies, function (body) {
physicsEngine.addBody(body.particle);
context.add(body.state).add(body);
});
var particles = _.map(bodies, function(body){
return body.particle;
});
_.each(particles, function(particle, index){
var r = new Repulsion({strength: 61});
var d = new Distance({length: 80, minLength: 0});
physicsEngine.attach(r, [particles[(index + 4) % 8]], particle);
physicsEngine.attach(r, [particle], particles[(index + 4) % 8]);
physicsEngine.attach(d, [particles[(index + 1) % 8]], particle);
if (index == 0) {
physicsEngine.attach(d, [particle], particles[7]);
} else {
physicsEngine.attach(d, [particle], particles[(index - 1) % 8]);
}
particle.setVelocity([0.004101*index,0.004101*index,0 ]);
});
_.each(walls, function(wall) {
physicsEngine.attach(wall);
});
Engine.on('prerender', function(){
_.each(bodies, function(body) {
body.state.setTransform(body.particle.getTransform());
});
});
I was looking for Drag!
var drag = new Drag({strength: 0.1})
physicsEngine.attach(drag, [particle]);
i want to sort a list of Locations by its Distance(displayed in list).
i already have a code that sould work but since i am that new to the whole mvc thing, i am not really sure where to place it to make it work.
Maybe someone can help me:
var geocoder = new google.maps.Geocoder();
var geo = Ext.create('Ext.util.Geolocation',{
autoUpdate: false,
listeners: {
locationupdate:{
scope: this,
fn: function(geo){
var haversindeDistance = function(lat1,lon1,lat2,lon2){
if(typeof(Number.prototype.toRad)=="undefined"){
Number.prototype.toRad = function(){
return this * Math.PI/180;
}
}
var R = 6371; //km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var lat1 = lat1.toRad();
var lat2 = lat2.toRad();
var a = Math.sin(dLat/2)*Math.sin(dLat/2)+
Math.sin(dLong/2)*Math.sin(dLon/2)*Math.cos(lat1)*Math.cos(lat2);
var c = 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
var d = R*c;
// KM or MIles
//return d*0.621371192; //MIles
return d;
};
var store = Ext.getStore('locationsstore');
store.suspendEvents(true);
store.each(function(location){
var lat2 = parseFloat(location.get(geocoder.geocode( { 'address': sAddress}, function(results, status) { })))||0;
var lon2 = parseFloat(location.get(geocoder.geocode( { 'address': sAddress}, function(results, status) { })))||0;
//var lat2 = parseFloat(location.get('lat'))||0;//try to put geocode on this ish
//var lon2 = parseFloat(location.get('lon'))||0;
if(lat2 && lon2){
var distance = haversineDistance(geo.getLatitude(),geo.getLongitude(),lat2,lon2);
location.set('distance',distance);
}
}, this);
store.resumeEvents();
store.filter('distance',/\d/);
store.sort('distance');//check if it is not done or can not be done somewhere else
list.setMasked(false);
}
},
locationerror:{
scope: this,
fn:function(geo,bTimeout,bPermissionDenied,bLocationUnavailable,message){
console.log([geo,bTimeout,bPermissionDenied,bLocationUnavailable,message]);
if(bTimeout){
Ext.Msg.alert('Timed out getting your location.');
}else{
Ext.Msg.alert('Error getting location. Please make sure location services are enabled on your Device.');
}
list.setMask(false);
}
}
}
});
geo.updateLocation();
i found the Solution.Just adding a Listener to the Navigation/List View should do it. I had too much in the Controller so i decided to put it directly into the View.