Getting rid of Chart.js canvas subpixels - javascript

I'm working on a really small widget which displays a simple bar chart:
I'm using Chart.js for that specific task.
var canvas = this.$(".chart-canvas")[0];
if (canvas) {
var ctx = canvas.getContext("2d");
ctx.translate(0.5, 0.5);
window.barChart = new Chart(ctx).Bar(barChartData, {
responsive: true,
maintainAspectRatio: false,
showScale: false,
scaleShowGridLines: false,
scaleGridLineWidth: 0,
barValueSpacing: 1,
barDatasetSpacing: 0,
showXAxisLabel: false,
barShowStroke: false,
showTooltips: false,
animation: false
});
As you can see, I've tried
ctx.translate(0.5, 0.5);
but that didn't really help.
Is there any way to get rid of the subpixel rendering?
I've read about Bresenham's line algorithm, but don't know how to implement it there.
Any ideas/suggestions appreciated.
Thank you in advance!

Assuming you have only one color, you can do this by extending the chart and overriding the draw to do a getImageData, "rounding" (if the pixel has a R, G or B value, set it to the color) the pixel colors and a putImageData.
You could do this for multiple colors too but it becomes a tad complicated when there are two colors close by.
However the difference in bar value spacing you are seeing is because of the way Chart.js calculates the x position for the bars - there's a rounding off that happens.
You can extend the chart and override the method that calculates the x position to get rid of the rounding off
Chart.types.Bar.extend({
// Passing in a name registers this chart in the Chart namespace in the same way
name: "BarAlt",
initialize: function (data) {
Chart.types.Bar.prototype.initialize.apply(this, arguments);
// copy paste from library code with only 1 line changed
this.scale.calculateX = function (index) {
var isRotated = (this.xLabelRotation > 0),
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
valueWidth = innerWidth / Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
if (this.offsetGridLines) {
valueOffset += (valueWidth / 2);
}
// the library code rounds this off - we don't
return valueOffset;
}
// render again because the original initialize call does a render
// when animation is off this is the only render that happens
this.render();
}
});
You'd call it like so
var ctx = document.getElementById('canvas').getContext('2d');
var myBarChart = new Chart(ctx).BarAlt(data, {
...
Fiddle - http://jsfiddle.net/gf2c4ue4/
You can see the difference better if you zoom in.
The top one is the extended chart

Related

Highchart Crosshair not touching x axis

I want to increase height of my xAxis crosshair to touch the x axis. Is there a way to achieve this. Below i have attached screen shot and code snippet for the same
Highcharts.chart(container, {
title: {
text: ""
},
xAxis: {
type: "datetime",
crosshair: true
}
}
I tried using it as tooltip option
tooltip: {
crosshairs: {
color: 'green',
width: 2,
height: 10
}
}
It takes width but does not take the height options
Js fiddle example
Currently the crosshair goes to where the x-axis baseline is expected to be (not accounting for offset), as also described by Mike Zavarello in the comments.
One workaround from my understanding of your situation is to extend Highcharts and instead draw the crosshair from the maximum value of your first y-axis (the one nearest the top) to the bottom of your second y-axis (the one nearest the bottom).
For example (JSFiddle):
(function (H) {
H.wrap(H.Axis.prototype, 'drawCrosshair', function (proceed, e, point) {
let old_function = H.Axis.prototype.getPlotLinePath;
H.Axis.prototype.getPlotLinePath = function (value, lineWidth, old, force, translatedValue) {
// copy paste handling of x value and sane value threshold
translatedValue = Math.min(Math.max(-1e5, translatedValue), 1e5);
x1 = x2 = Math.round(translatedValue + this.transB);
// max displayed value of your top y-axis
y1 = this.chart.yAxis[0].toPixels(this.chart.yAxis[0].max);
// min displayed value of your bottom y-axis
y2 = this.chart.yAxis[1].toPixels(this.chart.yAxis[1].min);
return this.chart.renderer.crispLine(
['M', x1, y1, 'L', x2, y2],
lineWidth || 1
);
};
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
H.Axis.prototype.getPlotLinePath = old_function;
});
}(Highcharts));
Note that this approach is very much directly addressing your problem by extending Axis.drawCrosshair, and within that extension rewriting the Axis.getPlotLinePath function to alter the path given for the crosshair. It does also not address crosshairs along the y-axis. Still, this could probably be solved in a similar way. It should be thoroughly tested for artifacts.
The length of the the crosshair is the same as xAxis height, so depending on the result you want to achieve set right xAxis height. Axis offset does not affect to the crosshair.
xAxis: {
crosshair: true,
height: 343, // yAxis[1].top - yAxis[0].top + yAxis[1].height
offset: -174
}
Live demo: http://jsfiddle.net/BlackLabel/w5c82ja9/

Updating Bokeh Plot in JS

I am developing in Javascript to render a table using a DataTables (Table plug-in for jQuery). I am also using Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect) to filter the table. I would like to re-render my BokehJS plot everytime the table is re-drawn. I have hooked up the correct calls but I am also calling Bokeh.Plotting.show to re-render the graph. This forces me to remove the last created div to avoid multiple graphs plotted. I am new to the JS side of Bokeh and wanted to understand if there is a cleaner method to update the plot in JS?
var eventFired = function ( type ) {
//var n = $('#demo_info')[0];
//n.innerHTML += '<div>'+type+' event - '+new Date().getTime()+'</div>';
//n.scrollTop = n.scrollHeight;
var plt = Bokeh.Plotting;
// set up some data
var M = 25;
var xx = [];
var yy = [];
var colors = [];
var radii = [];
for (var y = 0; y <= M; y += 4) {
for (var x = 0; x <= M; x += 4) {
xx.push(x * (Math.random() +0.5));
yy.push(y * (Math.random() +0.5));
colors.push(plt.color(50+8*x, 30+8*y, 150));
radii.push(Math.random() * 0.4 + 1.7)
}
}
// create a data source
var source = new Bokeh.ColumnDataSource({
data: { x: xx, y: yy, radius: radii, colors: colors }
});
// make the plot and add some tools
var tools = "pan,crosshair,wheel_zoom,box_zoom,reset,save";
var p = plt.figure({ title: type+' event - '+new Date().getTime(),
height: 300,
width: 300,
tools: tools });
// call the circle glyph method to add some circle glyphs
var circles = p.circle({ field: "x" }, { field: "y" }, {
source: source,
radius: radii,
fill_color: colors,
fill_alpha: 0.6,
line_color: null
});
//remove old plot on update conditions
$('.bk-root').remove();
// Show the plot, appending it to the end of the current
// section of the document we are in.
Bokeh.Plotting.show(p, document.getElementById('myplot'));
//To Do: add in transition to improve the UI appearance
}
And here is the the datatable setting up the call back to the BokehJS script.
</script>
$('#table').DataTable({
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal( {
header: function ( row ) {
var data = row.data();
return 'Details for '+data[0]+' '+data[1];
}
} ),
renderer: $.fn.dataTable.Responsive.renderer.tableAll( {
tableClass: 'table'
} )
}
},
paginate: false,
info: false,
paging: true,
autoWidth: true,
dom: '<"dt-buttons">Bf<"clear">lirtp',
"buttons": [ 'copy', 'csv', 'excel' ],
}).on( 'draw.dt', function () { eventFired( 'Draw' ); } );
Lastly, is there a good method to update the plot via a transition effect to improve the appearance of the re-plotting?
I eventually figured out all of the components in the process in a modified use case compared to the one shown above. In my present approach I needed to utilize my source data and emit a 'change' trigger.
source.data.factors = [my new factor list]
source.trigger('change');
The new factor list based on the jQuery datatable can be obtained as followed.
$('mytable').DataTable().columns(0, {search: 'applied'}).data()[0]
Also in my use case I am working with a categorical axis. In my specific use case, my factors will dynamically change upon redraw of the graph. This can be achieved using the plot's attributes.
p.attributes.x_range.factors = [ my new factor list from my updated source data]
There is also no need to remove the old plot, and the added plus is the redraw is very fast for my simple plots.

aframe:How to get pixels from canvas

I want to read and process the pixel data rendered in a-frame.
I tried the code below
var canvas = document.querySelector('canvas'),
params = {
preserveDrawingBuffer: true,
},
gl = canvas.getContext('experimental-webgl', params);
var pixels = new Uint8Array(canvas.width * canvas.height * 4);
gl.readPixels(
0,
0,
canvas.width,
canvas.height,
WebGLRenderingContext.RGBA,
WebGLRenderingContext.UNSIGNED_BYTE,
pixels
);
But the pixels array was left of 0, 0, 0, 0
How can I read the pixel data on the canvas?
I'd appreciate it if you could answer this problem
In the demo you posted, find and put a breakpoint on the line that says:
_gl = _context || _canvas.getContext( 'webgl', attributes )
Take a look at attributes, it's an options object, and among other settings it contains this option:
preserveDrawingBuffer: false
Although your original post shows you manually setting this option to true, your option has not carried through into the demo you posted. If you can make this option take effect, you should be able to read the pixels back.

Show Marker symbol on the newest data in HightChart

I am trying to create a plugin for our chart that shows a marker on the last data in the series of a data streams. The symbol should show on the newest data only. I learnt here that i can extend the prototypes and create my chart Here like
(function (H) {
H.wrap(H.Chart.prototype, 'init', function(proceed) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
};
H.wrap(H.Chart.prototype, 'redraw', function(proceed, points) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
//Add my marker symbol here
});
}(Highcharts));
I tested the two events above, they start add the right time. However, my points is a boolean value which is not what i was expecting. I read the api docs Here yet couldn't understand how to proceed. Please if some has a better example or guidance on how to use the extension and achieve this would highly be appreciated. My data is a stream and continuos data and need to show the the marker symbol on top of the newest data only.
Update
Thanks for the response sir. We are using react-highcharts and i noticed i don't have access to the chart load event. Nonetheless, i noticed i can extend my H.Series prototype as explained Here and my event is fired on every new tick paint. I now update my code to
H.wrap(H.Series.prototype, 'drawGraph', function (proceed) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
const { chart } = this;
renderSpotIndicator(chart);
});
function renderSpotIndicator(chart){
const series = chart.series[0],
len = series.data.length - 1,
lastPoint = series.data[len];
if(lastPoint){
lastPoint.update({
marker: {
enabled: true
}
});
}
}
When i run , then i noticed my marker property does not exist in my lastPoint as hence its throwing errors. I was not able to use that. I was advised to paint the marker instead. following the example Here . I changed my code to
function renderSpotIndicator(chart){
const series = chart.series[0],
len = series.data.length - 1,
lastPoint = series.data[len];
if(lastPoint){
chart.renderer.circle(lastPoint.pointX, lastPoint.pointY, 5).attr({
fill: lastPoint.color,
padding: 10,
r: 5,
stroke: 'white',
'stroke-width': 1,
zIndex: 5
}).add();
}
}
The above is throwing errors as well. When i checked, i noticed the property circled it self is not available on my lastPoint. My lastPoint is returning correct but i am not able to set the marker on the specific point for the property was not available on the lastPoint. Please any help would be appreciated sir.
You can edit the load event and extract last point of series. Then call update to show the marker. Each time when your chart is refreshed (by add point) you need to do the same steps. Extract last point, update marker (hide), add point and show last marker.
var series = this.series[0],
len = series.data.length - 1,
lastPoint = series.data[len];
lastPoint.update({
marker: {
enabled: true
}
});
setInterval(function() {
var x = (new Date()).getTime(), // current time
y = Math.random();
len = series.data.length - 1;
series.data[len].update({
marker: {
enabled: false
}
}, false);
series.addPoint([x, y], true, true);
len = series.data.length - 1;
series.data[len].update({
marker: {
enabled: true
}
}, true);
}, 1000);
Example:
- http://jsfiddle.net/ga2sym0v/

Flot Bubbles Plugin - Bubble Size

I am using the Bubbles plugin with the Flot charting library for JQuery. The data I have is dynamic and can be quite varied within the X, Y, and Z values. The main issue I am having is the size of the bubbles. If the X and Y values are somewhat close to each other but the Z value is much larger the bubble simply takes over the chart. Setting the axis min and max for the X and Y axes helps a bit but not in every case. I have tried to look for other options and settings but did not find anything useful. Is there any type of way to control the size of the bubble?
For instance Flex used to automatically create bubble sizes relative to the screen and axes where Flot seems to always set the bubble size to the same scale as the X and Y values. I have included just a sample of data. I would like to continue to use Flot as the plugin because I have many other chart types in my application and would like to use the same code base. However if there is another plugin that would be better I am open to ideas. Thanks!
https://jsfiddle.net/llamajuana/zd4hd7rb/
var d1 = [[30,339,139856], [30, 445,239823], [30,1506,127331]];
var options = {
series: {
//color: '#CCC',
color: function(x, y, value) {
var red = 55 + value * 10;
return 'rgba('+red+',50,50,1)';
},
bubbles: {
active: true,
show: true,
fill: true,
linewidth: 0,
bubblelabel: {
show: true
},
highlight: {
show: true,
opacity: 0.3
}
}
},
grid:{
hoverable: true,
clickable: true
},
tooltip: {
show: true,
content: "x: %x | y: %y | value: %ct"
}
};
var p4 = $.plot( $("#plot"), [d1], options );
You could try logarithmic scaling.
For the x- and y-axis you can do this using the transform property in the axis options or changing the data before drawing the plot.
For the bubbles you have to do this by hand, either by changing the data before drawing or by replacing the drawbubble function of the bubbles plugin (see the User draw example here).
See this fiddle for the full example. Changes from your fiddle:
1) You could change this directly in the bubbles plugin, if you wanted.
// index of bubbles plugin is dynamic, you better search for it
var defaultBubbles = $.plot.plugins[1].options.series.bubbles.drawbubble;
var logBubbles = function(ctx, serie, x, y, v, r, c, overlay){
defaultBubbles(ctx, serie, x, y, v, Math.log(r), c, overlay);
}
2) In the series options:
xaxis: {
transform: function (v) {
return Math.log(v);
},
inverseTransform: function (v) {
return Math.exp(v);
}
},
yaxis: {
transform: function (v) {
return Math.log(v);
},
inverseTransform: function (v) {
return Math.exp(v);
}
},
3) In the radiusAtPoint() function in the bubbles plugin:
// added Math.log function here too
return parseInt(series.yaxis.scale * Math.log(series.data[radius_index][2]) / 2, 0);

Categories

Resources