Show "No Data" message for Pie chart with no data - chart.js

I have below Piechart using chartjs. I need to show a "No Data" message on it when there is nothing to show.
But I am unable to find such a way in chartjs tutorials. Can someone please help?
Pie Chart HTML
<div id="canvas-holder-CT" style="width:46%;float:right;position: absolute; left: 0px; top: 700px;">
<canvas id="chart-area-CT" width="350" height="450" style="display: block; margin-left:2em">
</canvas>
<center> <b><details>
<summary>Distribution by Call Types</summary>
</details> </b></center>
</div>
Pie chart script
var configCT = {
type : 'pie',
data : {
datasets : [{
data : valuesCT,
backgroundColor : coloringCT,
label : 'Distribution by Call Types'
}
],
labels : labelsCT
},
options : {
segmentShowStroke : false,
legend : false,
animateScale : true,
responsive : true,
showAllTooltips : false,
tooltips : {
custom : function (tooltip) {
if (!tooltip)
return;
tooltip.displayColors = false;
}
}
}
};
var ctxCT = document.getElementById("chart-area-CT").getContext("2d");
if (myPieCT != null) {
myPieCT.destroy();
}
myPieCT = new Chart(ctxCT, configCT);

Have you checked this open issue?
And the relative solution proposed by etimberg user:
Chart.plugins.register({
afterDraw: function(chart) {
if (chart.data.datasets.length === 0) {
// No data is present
var ctx = chart.chart.ctx;
var width = chart.chart.width;
var height = chart.chart.height
chart.clear();
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = "16px normal 'Helvetica Nueue'";
ctx.fillText('No data to display', width / 2, height / 2);
ctx.restore();
}
}
});
See: https://jsfiddle.net/x04ptfuu/

Could be handle by CSS display property.
foo.html
<div id="showId" class="showData">
<canvas id="myChart"></canvas>
</div>
<div id="noId" class="noData">
<span>No Data Available</span>
<div>
foo.css
.showData {
display: block;
}
.noData {
display: none;
}
foo.js
// ...code
if(chartData) {
document.getElementById("showId").classList.add("showData");
document.getElementById("noId").classList.remove("noData");
} else {
document.getElementById("showId").classList.remove("showData");
document.getElementById("noId").classList.add("noData");
}

Related

How to replace Appendchild method in MapBox?

I have this problem when I try to load my map(Don't pay attention to the look of the sidebar, I'll fix it later). Everything looks fine but when I try to click on my markers,it finds the correct marker , but after 1-2 sec I am getting this error:"page doesn't exist enter a valid url and try again"
I did a lot of research,and now understand that the problem is in "appendchild". Also,If I am correct ,I can replace this functionality with " template for:each",right? I tried to do different options , but I am not sure If I am doing it correct... Can someone help me with this error? I put the code that worked before error:
css
body {
color: #404040;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
.sidebar {
position: absolute;
width: 33.3333%;
height: 100%;
top: 0;
left: 15;
overflow: hidden;
border-right: 1px solid rgba(0, 0, 0, 0.25);
color: rgb(19, 7, 48);
}
.map {
position: absolute;
left: 15%;
width: 85%;
top: 0;
bottom: 0;
}
js
import { LightningElement } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import mapbox from '#salesforce/resourceUrl/MapBoxJs';
export default class Test extends LightningElement {
connectedCallback() {
Promise.all([
loadStyle(this, mapbox + '/mapbox.css'),
loadScript(this, mapbox + '/mapbox.js'),
]).then(() => {
this.InitMap()
;
})
}
InitMap(){
L.mapbox.accessToken =
"pk*****";
const geojson = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [-108.99664191330069,45.27431839321315],
},
properties: {
title: "BUS 95",
description: "95 bus is coming",
"marker-color": "#FEF9A7",
"marker-size": "medium",
"marker-symbol": "95",
},
},
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [-109.04996965964688,45.238656126154666],
},
properties: {
title: "Bus 28",
description: "Bus 28 is coming...",
"marker-color": "#F5DF27",
"marker-size": "medium",
"marker-symbol": "28",
},
},
],
};
const map = L.mapbox
.map(
this.template.querySelector('.map')
)
.setView([45.281072930532304,-109.01104040481425], 14)
.addLayer(L.mapbox.styleLayer("mapbox://styles/mapbox/streets-v11"));
const listings = this.template.querySelector('.listings');
const locations = L.mapbox.featureLayer().addTo(map);
locations.setGeoJSON(geojson);
function setActive(el) {
const siblings = listings.getElementsByTagName("div");
for (const sibling of siblings) {
sibling.classList.remove("active");
}
el.classList.add("active");
}
locations.eachLayer((locale) => {
const prop = locale.feature.properties;
let popup = `<h3>${prop.title}</h3><div>${prop.description}`;
const listing = listings.appendChild(document.createElement("div"));
listing.className = "item";
const link = listing.appendChild(document.createElement("a"));
link.href = "title";
link.className = "title";
link.innerHTML = `${prop.title}`;
link.onclick = function () {
setActive(listing);
map.setView(locale.getLatLng(), 16);
locale.openPopup();
return false;
};
locale.on("click", () => {
map.panTo(locale.getLatLng());
setActive(listing);
});
locale.bindPopup(popup);
});
}
}
html
<template>
<div class="sidebar">
<div class="heading">
<h2>two busses</h2>
</div>
<div class="listings" lwc:dom="manual"></div>
</div>
<div class="map" lwc:dom="manual"></div>
</template>

Is it possible to create context menu with chart.js?

I wanted to create a context menu on my line graph. When the user hovers across a point, they can right click and will see a context menu so that they can trigger more action.
Is this possible with chart.js?
Thanks,
Derek
AFAIK there is no native support for context menu from Chart.js, but you can create it using HTML and handle it.
This is an example:
var timestamp = [],
speed = [10, 100, 20, 30, 40, 100, 40, 60];
for (var k = speed.length; k--; k>0) {
timestamp.push(new Date().getTime()-60*60*1000*k);
}
var canvas = document.getElementById('chart');
var BB = canvas.getBoundingClientRect(),
offsetX = BB.left,
offsetY = BB.top;
var ctx = canvas.getContext("2d");
var data = {
labels: timestamp,
datasets: [{
data: speed,
label: "speed",
backgroundColor: ['rgba(0, 9, 132, 0.2)'],
borderColor: ['rgba(0, 0, 192, 1)'],
borderWidth: 1,
}
]
};
var options = {
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}],
xAxes: [{
type: 'time',
}]
}
};
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: options
});
var $menu = $('#contextMenu');
canvas.addEventListener('contextmenu', handleContextMenu, false);
canvas.addEventListener('mousedown', handleMouseDown, false);
function handleContextMenu(e){
e.preventDefault();
e.stopPropagation();
var x = parseInt(e.clientX-offsetX);
var y = parseInt(e.clientY-offsetY);
$menu.css({left:x,top:y});
$menu.show();
return(false);
}
function handleMouseDown(e){
$menu.hide();
}
menu = function(n){
console.log("select menu "+n);
$menu.hide();
}
#chart {
width: 100%;
height: 100%;
}
#contextMenu{
position:absolute;
border:1px solid red;
background:white;
list-style:none;
padding:3px;
}
.menu-item:hover {
background: #dddddd;
text-decoration: underline;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id=canvasContainer>
<canvas id="chart"></canvas>
<ul id="contextMenu" style="display:none;">
<li class="menu-item" onclick="menu(1)">Menu 1</li>
<li class="menu-item" onclick="menu(2)">Menu 2</li>
</ul>
</div>
For anyone looking for this, #beaver's answer does not have access to the clicked item (data point), you can save the state in onHover and use it in contextmenu.
options: {
events: ['click', 'mousemove'],
onHover: function(e, fields){
const lx = e.layerX;
const bars = fields.filter(({_view: {x,width}}) => (x - width/2) <= lx && lx <= (x + width/2));
const data = bars.map(({_index, _datasetIndex}) => this.data.datasets[_datasetIndex].data[_index]);
this.hoveredItem = {bars, data, fields};
},
},
plugins: [
{
afterInit: (chart) =>
{
var menu = document.getElementById("contextMenu");
chart.canvas.addEventListener('contextmenu', handleContextMenu, false);
chart.canvas.addEventListener('mousedown', handleMouseDown, false);
function handleContextMenu(e){
e.preventDefault();
e.stopPropagation();
menu.style.left = e.clientX + "px";
menu.style.top = e.clientY + "px";
menu.style.display = "block";
console.log(chart.hoveredItem);
return(false);
}
function handleMouseDown(e){
menu.style.display = "none";
}
}
}
],
https://codepen.io/elizzk/pen/xxZjQvJ

Extjs Custom Combobox

how to create a custom combo like above?
here i just did a small hack to the component.by this way you can add any html element to the selection item in combo.
Ext.define('AMShiva.ux.custom.Combo', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.ux_combo',
colorField: 'color',//to get color value
displayField: 'text',
valueField:'value',
initComponent: function () {
var me = this;
// dropdown item template
me.tpl = Ext.create('Ext.XTemplate',
'<tpl for=".">',
'<div class="x-boundlist-item">',
'<span style="background-color: {' + me.colorField + '};" class="color-box-icon"></span>{' + me.displayField + '}',
'</div>',
'</tpl>'
);
me.callParent(arguments);
// here change the selection item html
me.on('change',
function(element, newValue) {
var inputEl = element.inputCell.child('input');
var data = element.getStore().findRecord(element.valueField, newValue);
if (data) {
inputEl.applyStyles('padding-left:26px');
var parent = inputEl.parent(),
spanDomEle = parent.child('span');
if (!spanDomEle) {
Ext.DomHelper.insertFirst(parent, { tag: 'span', cls: 'color-box-icon' });
var newSpanDomEle = parent.child('span');
newSpanDomEle.applyStyles('background-color: ' + data.get(element.colorField) + ';float: left;position: absolute;margin: 3px 2px 2px 4px;');
} else {
spanDomEle.applyStyles('background-color:' + data.get(element.colorField));
}
}
});
}
});
sample store:
var store = Ext.create('Ext.data.Store', {
fields: ['value', 'text', 'color']
});
css:
.color-box-icon {
width: 16px;
height: 16px;
margin: 0px 4px 0px -3px;
padding: 0px 8px;
}
Is there another way to do this kind of thing?

Scrollable Foundation Section headers

Looking through http://foundation.zurb.com/docs/components/section.html, is there anyway I can add horizontal scroll for Section headers ( Tabs) . I am looking something like http://www.seyfertdesign.com/jquery/ui.tabs.paging.html in foundation sections with horizontal scroll and continue to use accordion in small screen
I found a solution for those interested : https://codepen.io/gdyrrahitis/pen/BKyKGe
.tabs {
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
.tabs-title {
float: none;
display: inline-block;
}
}
if someone needs an angularjs with jquery implementation, below code can help you, for pure jquery replace angularjs directive method with a native js method with respective attributes.
I tried to search for similar implementation but found nothing, so I have written a simple angular directive which can transform a foundation CSS tabs to scrollable tabs
angular.module("app.directives.scrollingTabs", [])
.directive("scrollingTabs", ScrollingTabsDirective);
//#ngInject
function ScrollingTabsDirective($timeout, $window) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if(attr.scrollingTabs == "true"){
element.addClass('scrolling-tabs-container');
element.find('.nav-buttons').remove();
element.append('<div class="scrolling-tabs nav-buttons nav-buttons-left"></div>');
element.append('<div class="scrolling-tabs nav-buttons nav-buttons-right"></div>');
let scrolledDiv = $(element).find('.tabs');
let scrolled;
let scrolling;
let scrollFn = (step, animationTime, cb) => {
scrolled = Math.max(scrolled + step, 0);
scrolledDiv.animate({
scrollLeft: scrolled
}, animationTime, ()=>{
if (scrolling) {
scrollFn(step, animationTime, cb)
}else{
if(cb){cb()}
}
});
};
let checkActiveNavButtonsClasses = () => {
scrolled = scrolledDiv.scrollLeft();
let scrollWidth = scrolledDiv.get(0).scrollWidth;
let scrolledDivWidth = scrolledDiv.get(0).clientWidth;
if(scrollWidth > scrolledDivWidth){
element.addClass('nav-active');
scrollWidth = scrolledDiv.get(0).scrollWidth;
if(scrolled == 0){
element.removeClass('nav-active-left').addClass('nav-active-right')
}else if(scrolled > 0 && scrolled + scrollWidth < scrolledDivWidth){
element.addClass('nav-active-left').addClass('nav-active-right');
}else if(scrolled > 0 && scrolled + scrollWidth >= scrolledDivWidth){
element.addClass('nav-active-left').removeClass('nav-active-right');
}else{
element.removeClass('nav-active-left').removeClass('nav-active-right')
}
}else{
element.removeClass('nav-active-left').removeClass('nav-active-right').removeClass('nav-active');
}
};
let scrollToActiveTab = () => {
let activeDD = scrolledDiv.find('dd.active');
let tabsOffset = scrolledDiv.offset();
let activeTaboffset = activeDD.offset();
let activeTabwidth = activeDD.width();
let scrolledStep = activeTaboffset.left - tabsOffset.left - scrolledDiv.width() + activeTabwidth;
scrollFn(scrolledStep, 100, checkActiveNavButtonsClasses);
};
element.find(".nav-buttons.nav-buttons-left")
.off("click.scrolling")
.on("click.scrolling", (event)=>{
event.preventDefault();
scrolling = false;
scrollFn(-100, 100, checkActiveNavButtonsClasses);
})
.off("mouseover.scrolling")
.on("mouseover.scrolling", function (event) {
scrolling = true;
scrollFn(-2, 1, checkActiveNavButtonsClasses);
})
.off("mouseout.scrolling")
.on("mouseout.scrolling", function (event) {
scrolling = false;
});
element.find(".nav-buttons.nav-buttons-right")
.off("click.scrolling")
.on("click.scrolling", (event)=>{
event.preventDefault();
scrolling = false;
scrollFn(100, 100, checkActiveNavButtonsClasses);
})
.off("mouseover.scrolling")
.on("mouseover.scrolling", function (event) {
scrolling = true;
scrollFn(2, 1, checkActiveNavButtonsClasses);
})
.off("mouseout.scrolling")
.on("mouseout.scrolling", function (event) {
scrolling = false;
});
$timeout(()=>{
checkActiveNavButtonsClasses();
scrollToActiveTab()
},1000);
$($window).off('resize.scrolling').on('resize.scrolling', _.debounce(()=> {
checkActiveNavButtonsClasses();
}, 500));
scope.$on('$destroy', function() {
$($window).off('resize.scrolling');
});
}
}
}}
css:
.scrolling-tabs-container {
position: relative;
.tabs {
overflow-x: hidden;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
display: block;
margin-right: 18px;
dd {
display: inline-block;
float: none;
margin: 0px -3px 0px 0px;
}
.tabs-title {
float: none;
display: inline-block;
}
}
.scrolling-tabs {
&.nav-buttons {
display: none;
position: absolute;
width: 19px;
height: 38px;
border: 1px solid #c1c1c1;
top: 1px;
background-color: rgba(255, 255, 255, 0.5);
opacity: 0.4;
cursor: pointer;
&:hover {
opacity: 1;
&:before {
color: #444;
}
}
&:before {
position: absolute;
left: 7px;
top: 8px;
color: #777;
}
&.nav-buttons-left {
left: 0;
&:before {
content: '<';
}
}
&.nav-buttons-right {
right: 18px;
&:before {
content: '>';
}
}
}
}
&.nav-active{
.tabs{
margin-right: 36px;
margin-left: 18px;
}
.scrolling-tabs {
&.nav-buttons {
display: inline-block !important;
}
}
}
&.nav-active-left{
.scrolling-tabs{
&.nav-buttons-left{
opacity: 0.8;
}
}
}
&.nav-active-right{
.scrolling-tabs{
&.nav-buttons-right{
opacity: 0.8;
}
}}}
HTML: Foundation Tabs template.
<tabset class="list-tabs" scrolling-tabs="true">
<tab heading="tab1"></tab>
<tab heading="tab2"></tab>
<tab heading="tab2"></tab>
</tabset>
Before you start you'll want to verify that both jQuery (or Zepto) and foundation.js are available on your page. These come with foundation package so just uncomment them in your footer or include them accordingly.
<div class="section-container auto" data-section>
<section class="active">
<p class="title" data-section-title>Section 1</p>
<div class="content" data-section-content>
<p>Content of section 1.</p>
</div>
</section>
<section>
<p class="title" data-section-title>Section 2</p>
<div class="content" data-section-content>
<p>Content of section 2.</p>
</div>
</section>
</div>
The foundation documentation has all of the information for this :
http://foundation.zurb.com/docs/components/section.html#panel2
This will get you your section tabular headers. You then want to manage the content to be scrollable.
<div class="content" data-section-content>
<p>Content of section 1.</p>
</div>
This content here will be the area to work on, try adding a new class called .scrollable
Within this class use something like:
.scrollable{
overflow:scroll;
}
You may want to add some more to this however this will get you started. Your HTML should now look like this :
<div class="content scrollable" data-section-content>
<p>Content of section 1. This content will be scrollable when the content has exceeded that of the div size. </p>
</div>
This this is what you are looking for.

Group nodes together in Cytoscape.js

I am using Cytoscape for network visualization.
I have found this example of grouped nodes that you can expand and contract.
My question is, is it possible to highlight nodes and then dynamically add/remove them to a group on an ad-hoc basis?
I am yet to find an examples online of someone actually trying this and all the examples I have found like the aforementioned already have the groups predefined in the initial data load.
You can use the drag and drop extension of cytoscape.
Also, there is the clipboard extension, where you can select multiple elements via ctrl + mousedrag.
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [{
selector: 'node',
style: {
'label': 'data(id)'
}
},
{
selector: 'node:parent',
style: {
'label': ''
}
},
{
selector: 'edge',
style: {
'curve-style': 'bezier',
'target-arrow-shape': 'triangle'
}
},
{
selector: '.cdnd-grabbed-node',
style: {
'background-color': 'red'
}
},
{
selector: '.cdnd-drop-sibling',
style: {
'background-color': 'red'
}
},
{
selector: '.cdnd-drop-target',
style: {
'border-color': 'red',
'border-style': 'dashed'
}
}
],
elements: {
nodes: [{
data: {
id: 'a'
}
},
{
data: {
id: 'b'
}
},
{
data: {
id: 'c'
}
},
{
data: {
id: 'd',
parent: 'p'
}
},
{
data: {
id: 'p'
}
}
],
edges: [
]
}
});
var cdnd = cy.compoundDragAndDrop();
var removeEmptyParents = false;
var isParentOfOneChild = function(node) {
return node.isParent() && node.children().length === 1;
};
var removeParent = function(parent) {
parent.children().move({
parent: null
});
parent.remove();
};
var removeParentsOfOneChild = function() {
cy.nodes().filter(isParentOfOneChild).forEach(removeParent);
};
// custom handler to remove parents with only 1 child on drop
cy.on('cdndout', function(event, dropTarget) {
if (removeEmptyParents && isParentOfOneChild(dropTarget)) {
removeParent(dropTarget);
}
});
// custom handler to remove parents with only 1 child (on remove of drop target or drop sibling)
cy.on('remove', function(event) {
if (removeEmptyParents) {
removeParentsOfOneChild();
}
});
// toggle check handler
document.getElementById('remove-1ch-parents').addEventListener('click', function() {
removeEmptyParents = !removeEmptyParents;
if (removeEmptyParents) {
removeParentsOfOneChild();
}
});
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px;
}
#cy {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 1;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
#options {
z-index: 2;
position: absolute;
right: 0;
top: 0;
margin: 0.5em;
}
<head>
<title>cytoscape-compound-drag-and-drop demo</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-compound-drag-and-drop#1.0.0/cytoscape-compound-drag-and-drop.min.js"></script>
</head>
<body>
<h1>cytoscape-compound-drag-and-drop</h1>
<div id="cy"></div>
<div id="options">
<input id="remove-1ch-parents" type="checkbox" value="false" />
<label for="remove-1ch-parents">Remove parents with only one child</label>
</div>
</body>
The question itself is not that clear requirenment-wise, so if there is still something unclear, please edit it to be more precise (and maybe your code so that we can use that as a base. THX