Show bar with zero value in ChartJs v2 - bar-chart

I'm wondering is there any way to show in ChartJs (in bar chart) bars with zero value?
I mean something like this: https://jsfiddle.net/vrg5cnk5/16/, so the second bar would be just blue border on level zero.
I used already this code:
ticks: {
beginAtZero: true,
min: 0,
suggestedMin: 0
}
but I'm not surprised it didn't work.
Thanks in advance

Simply specify minBarLength in the dataset, with the minimum length in pixels the bars should have. See documentation.
Working Example:
var $chartCanvas = $('myCanvas');
var barChart = new Chart(myCanvas, {
type: 'bar',
data: {
labels: ['Accepted Answer', 'Top rated answer', 'This Answer'],
datasets:[{
data: [0, 3, 10],
minBarLength: 7, // This is the important line!
}]
},
options: {
title: {
display: true,
text: 'helpfulness of answers to this questions'
},
legend: {
display: false
}
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="myCanvas"></canvas>

After dig into the plugin system, if you using Chart.js >=2.5, you can write a plugin to achieve it. Here is an example to draw a line when data is zero.
Here is my code:
const zeroCompensation = {
renderZeroCompensation: function (chartInstance, d) {
// get postion info from _view
const view = d._view
const context = chartInstance.chart.ctx
// the view.x is the centeral point of the bar, so we need minus half width of the bar.
const startX = view.x - view.width / 2
// common canvas API, Check it out on MDN
context.beginPath();
// set line color, you can do more custom settings here.
context.strokeStyle = '#aaaaaa';
context.moveTo(startX, view.y);
// draw the line!
context.lineTo(startX + view.width, view.y);
// bam! you will see the lines.
context.stroke();
},
afterDatasetsDraw: function (chart, easing) {
// get data meta, we need the location info in _view property.
const meta = chart.getDatasetMeta(0)
// also you need get datasets to find which item is 0.
const dataSet = chart.config.data.datasets[0].data
meta.data.forEach((d, index) => {
// for the item which value is 0, reander a line.
if(dataSet[index] === 0) {
this.renderZeroCompensation(chart, d)
}
})
}
};
and here is how to add the plugin to Chart.js
var chart1 = new Chart(ctx, {
plugins: [zeroCompensation]
});
The offcial document is not clear about their plugin API, you may console.log to find what you want.

There is no way to configure chart.js to do this, but you could use a hack instead. Just set your value for the 0 bar to a really small number like 0.1.
data: [2, 0.1, 3, 1]
Here is an example forked from yours.
If you are using tooltips, then you would have to also add some logic so that the tooltip for that bar still displays a value of 0. You can do this using the label callback.
label: function(tooltipItem, data) {
var value = data.datasets[0].data[tooltipItem.index];
var label = data.labels[tooltipItem.index];
if (value === 0.1) {
value = 0;
}
return label + ': ' + value + ' %';
}
Here is an example putting it all together.

If you struggle with this, here's what I came up with. It is similar idea to Li Jinyao, but in addition, you would get click and hover events (tooltip) working for whole bar.
I value is close to 0 but negative, the bar will show on negative side of x axis - you can easily get rid of it if that's not what you want to do.
const zeroCompensation = {
id: 'zeroCompensation',
beforeDatasetsDraw: function(chart) {
const meta = chart.getDatasetMeta(0)
forEach(meta.data, d => {
const barHeight = d._view.base - d._view.y;
if(Math.abs(barHeight) < minBarHeight /* I used value 5 */) {
d._view.y = d._view.base - minBarHeight * (Math.sign(barHeight) || 1);
}
});
}};
and add it to plugins:
plugins: [zeroCompensation]
Keep in mind that this will work for values close to 0, not only 0. If you want it only for zeroes, you can change contents of if condition to:
chart.config.data.datasets[0].data[index] === 0
This is what Li Jinyao used in his answer.
Hope that helps.
Edit: I wanted to highlight that this solution works regardless of values spread. Answer marked as solution will not work as intended if there are some high values in data set - 0.1 will render same as 0 in that case.

Here is the simplest way to do this in V3 chart js
Chart.defaults.datasets.bar.minBarLength = 5;

2019 Update
This can be done easily as below.
var myChart = new Chart(ctx, {
...
options: {
...
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
);
You can find this in Chart.js documentation https://www.chartjs.org/docs/latest/

I just stumbled over this questions because I had a similar problem: The type of my Chart.js chart was 'horizontalBar' and for some datasets (where no zero values was present across the dataset) the horizontal bar did not start with 0 rather with the lowest value from the dataset.
I tried to figure out a solution and came up with following entry in the options object while creating the charts:
ticks: {
beginAtZero:true,
mirror:false,
suggestedMin: 0,
suggestedMax: 100
}
However that did not work as expected although all posts said it works that way.
After further investigation and reading of the Chart.js documentation I found the solution. The reason the further step did not work was following I found in the documentation:
However, any options specified on the x axis in a bar chart, are applied to the y axis in a horizontal bar chart.
So I just changed my options object to hold the proper configuration for the xAxes and it worked.
For those who are interested here is the the whole code I used for creating the horizontal bar chart with y-axis starting always at zero:
this.chart = new Chart(
ctx,
{
type: 'horizontalBar',
data: this.data.chartdata,
options: {
scales: {
xAxes: [{
stacked: false,
ticks: {
beginAtZero:true,
mirror:false,
suggestedMin: 0,
suggestedMax: 100
}
}],
yAxes: [{
stacked: true
}]
},
scaleBeginAtZero : true,
// important here to use () =>
// to keep the scope of this
onClick: (e) => {
var actChart : Chart = this.charts[trialId];
var element =
actChart.getElementAtEvent(e);
}
}
}
);

Related

Use Data Visualization Extension multiple times

I have two separate extensions visualizing different "datapoints" from different domains (measurements like temperature, humidity, ... and image locations). These extensions should remain separated (and should not know each other).
There is no problem when using the extensions independent of each other. But problems occur, if both extensions are used at the same time (for the same model) in the Forge Viewer.
Both extensions try to get an existing instance of the DataVis-extension and load the extension if it is not available (call to viewer.loadExtension in each custom extension no difference).
The extensions create ViewableData and add viewables to it (sharing a single instance of ViewableData makes no difference).
After adding viewables await viewableData.finish() is called and the addViewables-method of the DataVis-extension is called.
One of the main problems is, that method changeOcclusion changes the occlusion only of the viewables which were added last. The other viewables remain visible at any time.
Probably because the pointMaterial in the DataVis-extension is overwritten any time the addViewables extension is called.
Is there a way to instantiate the extension multiple times, so that it is guaranteed that there are no side-effects when using it from within different custom extensions? Or maybe other mechanisms?
After consulting our engineering team, we submitted an issue report, LMV-6574, for tracking this issue. Please take note of the LMV prefixed id for tracking. We're welcome to ask for updates in the future by sending the issue id to forge (DOT) help (AT) autodesk (DOT) com.
However, we don't want to stop your development, so here is a workaround.
As I mentioned in the above comments area, SpriteViewable's constructor accepts an argument, ViewableStyles, that is used to set up the sprite icon. So, you don't need to call DataVisualization.addViewables(data) twice. Before finishing the ViewableData, you can add viewables with different ViewableStyles without a doubt.
Back to your use case, you want to reuse the DataVisualization extension for different data sources. To do so, I would advise you to store your device (sensor) data separately (e.g. device container). When you need to add/remove devices, just modify the device container, clear viewables, and then add new viewalbe data among your device container.
Here are some code snippets demonstrating this idea:
Example code for initializing:
let sensorStyleDefinitions = {
co2: {
url: "http://localhost:3000/assets-1/images/co2.svg",
color: 0xffffff,
},
temperature: {
url: "http://localhost:3000/assets-1/images/thermometer.svg",
color: 0xffffff,
},
default: {
url: "http://localhost:3000/assets-1/images/circle.svg",
color: 0xffffff,
},
};
// Create model-to-style map from style definitions.
let styleMap = {};
Object.entries(sensorStyleDefinitions).forEach(([type, styleDef]) => {
styleMap[type] = new Autodesk.DataVisualization.Core.ViewableStyle(
Autodesk.DataVisualization.Core.ViewableType.SPRITE,
new THREE.Color(styleDef.color),
styleDef.url
);
});
let devices = [
{
id: "Hall I",
position: {
x: -14.297511041164398,
y: -77.6432056427002,
z: 11.31889820098877,
},
type: "temperature",
sensorTypes: ["temperature"],
},
{
id: "Hall IV",
position: {
x: 60.53697395324707,
y: -74.6432056427002,
z: 11.31889820098877,
},
type: "co2",
sensorTypes: ["co2"],
},
];
const viewableData = new Autodesk.DataVisualization.Core.ViewableData();
viewableData.spriteSize = 16;
// Add viewables
devices.forEach((device, index) => {
const style = styleMap[device.type] || styleMap["default"];
const viewable = new Autodesk.DataVisualization.Core.SpriteViewable(
device.position,
style,
index + 1
);
viewableData.addViewable(viewable);
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);
Example code for adding a device(sensor)
devices.push({
id: "Hall XII",
position: {
x: -15,
y: -70,
z: 50,
},
type: "temperature",
sensorTypes: ["temperature"],
});
// Remove existing sprites
dataVizExt.removeAllViewables();
const viewableData = new Autodesk.DataVisualization.Core.ViewableData();
viewableData.spriteSize = 16;
// re-add viewables
devices.forEach((device, index) => {
const style = styleMap[device.type] || styleMap["default"];
const viewable = new Autodesk.DataVisualization.Core.SpriteViewable(
device.position,
style,
index + 1
);
viewableData.addViewable(viewable);
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);
Example code for removing a device(sensor)
devices = devices.splice(1, 1);
// Remove existing sprites
dataVizExt.removeAllViewables();
const viewableData = new Autodesk.DataVisualization.Core.ViewableData();
viewableData.spriteSize = 16;
// re-add viewables
devices.forEach((device, index) => {
const style = styleMap[device.type] || styleMap["default"];
const viewable = new Autodesk.DataVisualization.Core.SpriteViewable(
device.position,
style,
index + 1
);
viewableData.addViewable(viewable);
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);

Make tidier table and styling problem on jspdf

I have some div in my html page i want to make it as pdf. It's the page with multiple table, and each header / table start in the new page.
The one i'm using is jspdf, and i already achieve my goal of starting the new page based of div class. The code i try is this one :
https://plnkr.co/edit/DmNuINbijP1tu4cW
$('#printbutton').on("click", function () {
var pdf = new jsPDF('landscape');
// var pdf = new jsPDF('p','pt','a4');
var pdfName = 'test.pdf';
var imagesToResize = document.getElementsByTagName("img");
for(i=0;i<imagesToResize.length;i++){
imagesToResize[i].style.width = "100px";
imagesToResize[i].style.height = "100px";
}
var options = { pagesplit: true};
var $divs = $('.myDivClass')
//jQuery object of all the myDivClass divs
var numRecursionsNeeded = $divs.length -1; //the number of times we need to call addHtml (once per div)
var currentRecursion=0;
//Found a trick for using addHtml more than once per pdf. Call addHtml in the callback function of addHtml recursively.
function recursiveAddHtmlAndSave(currentRecursion, totalRecursions){
//Once we have done all the divs save the pdf
if(currentRecursion==totalRecursions){
pdf.save(pdfName);
}else{
currentRecursion++;
pdf.addPage();
//$('.myDivClass')[currentRecursion] selects one of the divs out of the jquery collection as a html element
//addHtml requires an html element. Not a string like fromHtml.
pdf.fromHTML($('.myDivClass')[currentRecursion], 15, 20, options, function(){
console.log(currentRecursion);
recursiveAddHtmlAndSave(currentRecursion, totalRecursions)
});
}
}
pdf.fromHTML($('.myDivClass')[currentRecursion], 15, 20, options, function(){
recursiveAddHtmlAndSave(currentRecursion, numRecursionsNeeded);
});
});
The problem is, the table is kinda messy, with teh width not fully the same as the paper and somehow it's making two row showing on one page (and there's hollow row at the start of the page)
Is there anything wrong in the code? my goal is for make the table readable and at least > 10 row showed at the same page.
Thank u in advance
The tableWidth: 'auto' should work perfectly. Anyways,
I tried the following style and properties to my table and it works fine.
pdf.autoTable(res2.columns, res2.data, {
startY: false,
theme: 'grid',
tableWidth: 'auto',
columnWidth: 'wrap',
showHeader: 'everyPage',
tableLineColor: 200,
tableLineWidth: 0,
columnStyles: {
0: {
columnWidth: 50
},
1: {
columnWidth: 50
},
2: {
columnWidth: 50
},
3: {
columnWidth: 50
},
4: {
columnWidth: 50
},
5: {
columnWidth: 'auto'
},
6: {
columnWidth: 50
},
7: {
columnWidth: 50
},
8: {
columnWidth: 'auto'
}
},
headerStyles: {
theme: 'grid'
},
styles: {
overflow: 'linebreak',
columnWidth: 'wrap',
font: 'arial',
fontSize: 10,
cellPadding: 8,
overflowColumns: 'linebreak'
},
});

How can we increase the fonsize of ag-grid data on context menu action?

I am using Angular 6 and ag grid version 18. I want to have an option from the context menu to increase and decrease the fontsize
I want a feature something like this. I tried rowstyle,cellstyle that did not work
Below is how the column definition looks like. Note I am using variable a for incrementing and decrementing the font size -
var a = 10;
var columnDefs = [
{headerName: 'Athlete', field: 'athlete', width: 150,
cellStyle: function(params) {
return {fontSize: params.context.a + 'px', backgroundColor: 'green'};
}
},
..
];
Set the reference to your component as below, this will be used in context menu callbacks -
var gridOptions = {
context: this,
..
};
Below is the cell refresh and context menu -
function refreshCells() {
var params = {
force: true
};
gridOptions.api.refreshCells(params);
}
function getContextMenuItems(params) {
var result = [
{
// custom item
name: 'Increase Font',
action: function() {
params.context.a = params.context.a + 5;
params.context.refreshCells();
},
},
{
// custom item
name: 'Decrease Font',
action: function() {
params.context.a = params.context.a - 5;
params.context.refreshCells();
},
}
];
return result;
}
Please see Plunkr -
Increase/decrease font ag-grid plunkr

Resizing a js chart in shiny app

I am using a gauge widget from c3 library.
It's function are composed by 3 elements:
function(Value, width, height)
When I try to generate the dashboard, the result is this one:
I am using this layout:
splitLayout(C3GaugeOutput("gauge1","auto","auto"),
C3GaugeOutput("gauge2","auto","auto"),
C3GaugeOutput("gauge3","auto","auto"))
I tryied changed the dimensions, but this side bar still appears.
The C3 code is the following:
HTMLWidgets.widget({
name: 'C3Gauge',
type: 'output',
factory: function(el, width, height) {
return {
renderValue: function(x) {
// Check if we have a reference to our chart
if(typeof(el.chart) == 'undefined'){
// create a chart and set options
// note that via the c3.js API we bind the chart to the element with id equal to chart1
var chart = c3.generate({
bindto: el,
data: {
json: x,
type: 'gauge',
},
gauge: {
label:{
//returning here the value and not the ratio
format: function(value, ratio){ return value;}
},
min: 0,
max: 100,
width: 15,
units: '%' //this is only the text for the label
}
});
el.chart = chart;
}else{
// Update the chart if it already exists
el.chart.load({json: x});
}
},
resize: function(width, height) {
// TODO: code to re-render the widget with a new size
}
};
}
});
Solved:
Using box() function:
fluidRow(box(C3GaugeOutput("gauge1","auto","auto")),
box(C3GaugeOutput("gauge2","auto","auto")),
box(C3GaugeOutput("gauge3","auto","auto")))

draggable rows in a p:dataTable by handle

I have a DataTable in which I added drag and drop support for the rows (draggableRows="true"). The problem is that wherever I click inside a row, I can drag it.
What I want is the possibility to drag the row only by a handle, the handle could be a column field with an icon at the left of the row for example (have a look at the screenshot), so if the user clicks on a row outside of the handle, there's no drag support; but if he clicks on the handle, he'll have the possibility to drag the entire row.
How could I implement this?
The source is always with you. In there you can see the makeRowsDraggable function on line 2727 in datatable.js
makeRowsDraggable: function() {
var $this = this;
this.tbody.sortable({
placeholder: 'ui-datatable-rowordering ui-state-active',
cursor: 'move',
handle: 'td,span:not(.ui-c)',
appendTo: document.body,
start: function(event, ui) {
ui.helper.css('z-index', ++PrimeFaces.zindex);
},
...
}
with a reference to the handle ('td, span:not(.ui-c)').
By overriding this function and having the handle point to a selector that explicitly refers to your handle, you can 'fix' it.
You can even make this generic by not assigning an explict string to the handle, but but looking it up on e.g. a custom pass-through attribute you define on the datatable where you put the 'string' in.
Did I mention already that the source is always with you? Good thing to remember when having further questions
Since Primefaces 6.2 p:datatable has a property rowDragSelector specifically for this purpose. See the example below:
<p:dataTable value="#{myBean.entities}" id="myTablePreferredId" rowDragSelector=".draggableHandle" draggableRows="true">
<p:ajax event="rowReorder" listener="#{myBean.onRowReorder}"/>
<p:column>
<h:outputText styleClass="fa fa-arrows-v draggableHandle" />
</p:column>
...
</p:dataTable>
For more details refer to the primefaces documentation.
My solution is the same as the solution of #Kuketje.
Here is the source code (compatible with Primefaces 6.1)
if (PrimeFaces.widget.DataTable){
PrimeFaces.widget.DataTable = PrimeFaces.widget.DataTable.extend({
makeRowsDraggable: function () {
var $this = this,
draggableHandle = '.dnd-handle'; //change to what ever selector as you like
this.tbody.sortable({
placeholder: 'ui-datatable-rowordering ui-state-active',
cursor: 'move',
handle: draggableHandle,
appendTo: document.body,
start: function (event, ui) {
ui.helper.css('z-index', ++PrimeFaces.zindex);
},
helper: function (event, ui) {
var cells = ui.children(),
helper = $('<div class="ui-datatable ui-widget"><table><tbody></tbody></table></div>'),
helperRow = ui.clone(),
helperCells = helperRow.children();
for (var i = 0; i < helperCells.length; i++) {
helperCells.eq(i).width(cells.eq(i).width());
}
helperRow.appendTo(helper.find('tbody'));
return helper;
},
update: function (event, ui) {
var fromIndex = ui.item.data('ri'),
toIndex = $this.paginator ? $this.paginator.getFirst() + ui.item.index() : ui.item.index();
$this.syncRowParity();
var options = {
source: $this.id,
process: $this.id,
params: [
{name: $this.id + '_rowreorder', value: true},
{name: $this.id + '_fromIndex', value: fromIndex},
{name: $this.id + '_toIndex', value: toIndex},
{name: this.id + '_skipChildren', value: true}
]
}
if ($this.hasBehavior('rowReorder')) {
$this.cfg.behaviors['rowReorder'].call($this, options);
}
else {
PrimeFaces.ajax.Request.handle(options);
}
},
change: function (event, ui) {
if ($this.cfg.scrollable) {
PrimeFaces.scrollInView($this.scrollBody, ui.placeholder);
}
}
});
}
});
}
The solution from Vít Suchánek is not really working. It detects the drag&drop handle only when the page is ready. After the first drag&drop interaction, it is not going to work anymore.
Another possibility is to override Primefaces's setting of handler after initialization of UI sortable:
<script type="text/javascript">
$(document).ready(function() {
var sortableRows = $(".tableWithDraggableRows > tbody");
if (sortableRows) {
sortableRows.sortable("option", "handle", ".ui-icon-arrow-4");
}
});
</script>
See http://api.jqueryui.com/sortable/#option-handle