Given my data names can be super long, I want to implement a tooltip when the user hover the data name, however, I can't find any documentation on adding custom tags to the generated data name html. One can add a class but not data tag.
Example
The way too long data name in that case. A tooltip would be perfect.
Ok I understood what you wanted :)
We can just use a couple extra functions in our c3.generate call and extend the library a bit, this will give you some more flexibility. Just define your long labels inside the oninit function.
Here is the jsFiddle, hover over the legend to see:
https://jsfiddle.net/abacaj90/6v2tpft2/14/
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function legendFollowMouse(e) {
var x = e[0];
var y = e[1];
return {
x: x - 50 + 'px',
y: y + 20 + 'px'
}
}
function createLegendTooltip() {
var svg = this.svg[0][0];
var frag = document.createDocumentFragment();
var div = document.createElement('div');
var span = document.createElement('span');
div.className = 'c3-legend-tooltip-container';
span.className = 'c3-legend-tooltip';
div.appendChild(span);
frag.appendChild(div);
insertAfter(svg, frag);
this.legendHoverNode = span;
}
function generateLegendHoverLabels(labels) {
createLegendTooltip.call(this);
var obj = {};
this.data.targets.map(function(data, i) {
if(typeof labels[i] !== 'undefined') {
obj[data.id] = data.id + ': ' + labels[i];
}
})
return obj;
}
var chart = c3.generate({
data: {
columns: [
['data1', 30, 200, 100, 400, 150, 250],
['data2', 20, 180, 240, 100, 190, 250],
['data3', 20, 180, 240, 100, 190, 250]
],
},
oninit: function() {
// declare your extra long labels here
var legendLongLabels = ['long content here, data3 doesnt have a tooltip!','even longer content here, you can style me with css!'];
this.legendHoverContent = generateLegendHoverLabels.call(this, legendLongLabels);
},
legend: {
item: {
onmouseover: function (id) {
// keep default behavior as well as our tooltip
d3.select(this.svg[0][0]).classed('c3-legend-item-focused', true);
if (!this.transiting && this.isTargetToShow(id)) {
this.api.focus(id);
}
// if we defined the long labels, display them
if (this.legendHoverContent.hasOwnProperty(id)) {
var coords = legendFollowMouse(d3.mouse(this.svg[0][0]))
this.legendHoverNode.parentNode.style.display = 'block';
this.legendHoverNode.parentNode.style.top = coords.y;
this.legendHoverNode.parentNode.style.left = coords.x;
this.legendHoverNode.innerHTML = this.legendHoverContent[id];
}
},
onmouseout: function (id) {
// keep default behavior as well
d3.select(this.svg[0][0]).classed('c3-legend-item-focused', false);
this.api.revert();
// just hide the tooltips
this.legendHoverNode.parentNode.style.display = 'none';
}
}
}
});
Temporary answer while waiting for something better:
This is the input Data Im using.
var data = {
"data": [{
"A": "1075.000000",
"date": "01-03-2016"
}, {
"A": "878.571429",
"date": "01-04-2016"
}, {
"A": "485.000000",
"date": "04-03-2016"
}, {
"A": "795.000000",
"date": "05-03-2016"
}, {
"A": "620.000000",
"date": "06-03-2016"
}, {
"A": "957.500000",
"date": "07-03-2016"
}, {
"name": "H1W",
"A": "990.000000"
}, {
"A": "950.000000",
"date": "09-03-2016"
}, {
"A": "680.000000",
"date": "10-03-2016"
}, {
"A": "1000.000000",
"date": "17-03-2016"
}, {
"A": "535.000000",
"date": "18-02-2016"
}],
"name": "A",
"namePlus": {
"location": "B or C or D or E",
"vars": {
"sizes": "Sizes: 2 or 3 or 5",
"unitSizes": "Units Amount: (0 to 2) or (3 to 5)"
}
}}
This is how I process the input
var tooltip = "<ul><li>" + data.name + "</li><li>" + data.namePlus.location + "</li>";
$.each(data.namePlus.vars, function (key, value) {
tooltip += "<li>" + value + "</li>";
});
tooltip += "</ul>";
c3.generate({
bindto: '#lineChart',
data: {
json: data.data,
keys: {
x: 'date',
value: [data.name]
},
xFormat: '%d-%m-%Y',
classes: {
value: 'HEY'
}
});
$(".c3-legend-item-" + data.name).mouseover(function (e) {
$(".myTooltip").html(tooltip).css({visibility: "visible"});
});
$(".c3-legend-item-" + data.name).mouseleave(function (e) {
$(".myTooltip").html(tooltip).css({visibility: "hidden"});
});
And this is the actual css
.myTooltip {
background-color: black;
border-radius: 6px;
color: #fff;
font-size: 12px;
height: 25px;
margin: auto;
opacity: 0.68;
padding: 5px 0;
position: relative;
text-align: center;
top: 14px;
visibility: hidden;
width: 50%;}
Related
I have a simpel doughnut chart, made with the following code:
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["Buchen (65%)", "Eschen (11%)", "Ahorn (8%)", "Eichen, Linden und weitere Laubhölzer (11%)", "Nadelholz (5%)"],
datasets: [
{
backgroundColor: ["#2F4F4F", "#008080","#2E8B57","#3CB371","#3AC9A3"],
data: [65,11,8,11,5]
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
tooltips: {
enabled: false
},
plugins: {
legend: {
onClick: (e) => e.stopPropagation(),
display: true,
position: 'right',
}
}
}
});
which turns into:
How to remove the '65' at the very end of the tooltip which pops up while hovering?
I came to understand that callbacks make it possible to customize the tooltip, however not yet managed this edit via the documentation.
I think this is the way you want to see the hover over tooltips:
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["Buchen (65%)", "Eschen (11%)", "Ahorn (8%)", "Eichen, Linden und weitere Laubhölzer (11%)", "Nadelholz (5%)"],
datasets: [{
backgroundColor: ["#2F4F4F", "#008080", "#2E8B57", "#3CB371", "#3AC9A3"],
data: [65, 11, 8, 11, 5]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
onClick: (e) => e.stopPropagation(),
display: true,
position: 'right',
},
tooltip: {
callbacks: {
label: function(context) {
return context.label;
}
}
}
}
}
});
After testing this might be the solution and also soft coded the labels. As your labels were hard coded and if the values would change it should match up automatically.
Added a video as well breaking it down: https://youtu.be/b6oVAcQijIw
// Created an array to soft code your values in the labels.
const datavalue = [65,11,8,11,5];
const datalabels = ['Buchen', 'Eschen', 'Ahorn', 'Eichen, Linden und weitere Laubhölzer', 'Nadelholz'];
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: [datalabels[0], datalabels[1] + ' (' + datavalue[1] +'%)', datalabels[2] + ' (' + datavalue[2] +'%)', datalabels[3] + ' (' + datavalue[3] +'%)', datalabels[4] + ' (' + datavalue[4] +'%)'],
datasets: [
{
backgroundColor: ["#2F4F4F", "#008080","#2E8B57","#3CB371","#3AC9A3"],
data: datavalue
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
tooltips: {
enabled: false
},
plugins: {
legend: {
onClick: (e) => e.stopPropagation(),
display: true,
position: 'right',
},
// For the tooltipItem is the trigger of the hover effect.
tooltip: {
callbacks: {
label: function(tooltipItem){
let label = myChart.data.labels[tooltipItem.dataIndex];
let value = myChart.data.datasets[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
return label;
}
}
}
}
}
});
Just read the description and I noticed you are gonna change tooltips.
Okay, I am gonna write how I solve that problem.
tooltips: {
titleFontSize: 15,
titleMarginBottom: 10,
bodyFontSize: 15,
bodySpacing: 5,
xPadding: 15,
yPadding: 10,
enabled: false,
callbacks: {
label: function (context, data) {
return parseFloat(context.value).toFixed(1);
},
title: function (context) {
return mediaList.length;
},
},
custom: function (tooltipModel) {
let tooltipEl = document.getElementById("chartjs-tooltip");
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement("div");
tooltipEl.id = "chartjs-tooltip";
tooltipEl.innerHTML = "<table></table>";
document.body.appendChild(tooltipEl);
}
tooltipEl.classList.add("tooltip-wrapper");
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove("above", "below", "no-transform");
if (tooltipModel.yAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
} else {
tooltipEl.classList.add("no-transform");
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltipModel.body) {
let titleLines = tooltipModel.title || [];
let bodyLines = tooltipModel.body.map(getBody);
let innerHtml = "<thead>";
titleLines.forEach(function (title) {
innerHtml +=
"<tr><th style='text-align: left'>" + title + "</th></tr>";
});
innerHtml += "</thead><tbody>";
bodyLines.forEach(function (body, i) {
const colors = tooltipModel.labelColors[i];
let style = "background:" + colors.backgroundColor;
style += "; border-color:" + colors.borderColor;
style += "; border-width: 2px";
style += "; width: 15px";
style += "; height: 15px";
style += "; display: inline-block";
style += "; margin: 0px 5px";
style += "; border-radius: 50%";
let span = '<span style="' + style + '"></span>';
innerHtml +=
"<tr><td style='display: flex;align-items: center'>" +
parseFloat(body[0].split(":")[1]).toFixed(1) +
"%" +
span +
body[0].split(":")[0] +
"</td></tr>";
});
innerHtml += "</tbody>";
var tableRoot = tooltipEl.querySelector("table");
tableRoot.innerHTML = innerHtml;
}
// `this` will be the overall tooltip
const position = this._chart.canvas.getBoundingClientRect();
// Display, position, and set styles for font
tooltipEl.style.backgroundColor = "rgb(45 54 89 / 75%)";
tooltipEl.style.color = "white";
tooltipEl.style.borderRadius = "6px";
tooltipEl.style.opacity = 1;
tooltipEl.style.position = "absolute";
tooltipEl.style.width = "max-content";
tooltipEl.style.transform = "translate(-106%, -50%)";
tooltipEl.style.left =
position.left + window.pageXOffset + tooltipModel.caretX + "px";
tooltipEl.style.top =
position.top + window.pageYOffset + tooltipModel.caretY + "px";
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + "px";
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
tooltipEl.style.padding =
tooltipModel.yPadding + "px " + tooltipModel.xPadding + "px";
tooltipEl.style.pointerEvents = "none";
},
},
As you can see here, you should create a custom tooltip in the tooltip option.
Under Tooltip Callbacks there is a reference to modifying the tooltip label: https://www.chartjs.org/docs/3.0.2/configuration/tooltip.html#label-callback
I think you need to add something like this:
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context) {
var newLabel = context.label || '';
var lastIndexOfSpace = newLabel.lastIndexOf(' ');
if (lastIndexOfSpace > 0) {
newLabel = newLabel.substring(0, lastIndexOfSpace);
}
return newLabel;
}
}
}
}
}
I am using google visualization bubble chart, I need to align the vertical axis labels something like below, I want to align the labels to the margin of the chart not to the axis line, also need 2 lines and extend the major grid line to outside of the chart area.
Also here is the code :
<div data-ng-app="mainApp" data-ng-controller="mainSearchController"
ng-init="ShowChart()">
<div class="row" ng-mouseover="mousepoints($event)">
<div google-chart chart="saleChart"
agc-on-mouseover="showTooltip(row)"
agc-on-mouseout="hideTooltip()">
</div>
<div id="custom_tooltip"
style="position:fixed; border:0px solid #777777;
padding-left:10px; line-height:15px; color:#5f5f5f;
font-family:Arial; background-color:#FFFFFF;
height:auto; width:auto; font-size:10px;">
</div>
</div>
</div>
And here is the angularjs code to bind the chart
var app = angular.module('mainApp', ['googlechart']);
app.controller('mainSearchController', function ($scope) {
$scope.ShowChart = function () {
var saleChart = {};
saleChart.type = 'BubbleChart';
saleChart.cssStyle = "height:100%; width:100%;";
var options = {
sizeAxis: {
maxSize: 7,
minSize: 1
},
fontSize:10,
legend: 'none',
height: 200,
width: 400,
bubble: { stroke: '#fdca0f', opacity: 1 },
colors: ['#fdca0f', '#fdca0f'],
tooltip: {
trigger: 'none'
},
hAxis: {
ticks: [
{ v: 800, f: '2015' },
{ v: 1200, f: '2016' },
{ v: 1600, f: '2017' },
{ v: 2000, f: '2018' },
{ v: 2400, f: '2019' },
{ v: 2800, f: '2020' }
],
gridlines: { color: '#dedede' },
minorGridlines: { color: '#f7f7f7', count: 3 },
textStyle: { color: '#5f5f5f' }
},
vAxis: {
ticks: [
{ v: 1, f: 'Chennai in March' },
{ v: 2, f: 'Mumbai in March' },
{ v: 3, f: 'Delhi in April' },
{ v: 4, f: 'Chennai in April' }
],
gridlines: { color: '#dedede' },
textStyle: { color: '#5f5f5f' }
}
};
var d = [
["Name", "Year", "Place", "", "Sales", "tooltip"],
["", 1000, 2, "", 26, "Sale List"],
["",1200,3,"",28,"Sale List"],
["",1400,3,"",48,"S"],
["",1600,3,"",29,"S"]
];
saleChart.data = d;
$scope.chartData = d;
saleChart.options = options;
$scope.saleChart = saleChart;
}
var mouseX;
var mouseY;
$scope.mousepoints = function (e) {
mouseX = e.pageX;
mouseY = e.pageY;
}
$scope.showTooltip = function (row) {
var x = mouseX;
var y = mouseY + 10;
if (row != null) {
dataTable = google.visualization.arrayToDataTable($scope.chartData);
var v = dataTable.getValue(row, 5);
//var v = $scope.chartData.rows[row][5];
v = v.toString().replace(/,/g, "<br/>")
$('#custom_tooltip').html('<div>' + v + '</div>').css({
'top': y,
'left': x
}).fadeIn('slow');
}
}
$scope.hideTooltip = function () {
$('#custom_tooltip').fadeOut('fast');
}
});
the requested changes can only be made by manually modifying the chart's SVG,
this can be done on the chart's 'ready' event.
first, add the ready event to the <div google-chart> element...
<div google-chart chart="saleChart" agc-on-ready="onReady(chartWrapper)"
agc-on-mouseover="showTooltip(row)" agc-on-mouseout="hideTooltip()">
</div>
then add the listener to the controller...
in order to move the labels down, find the <text> elements,
and the change their 'y' attribute.
as for the grid lines (<rect>), we need to change the 'x' attribute, as well as the 'width'.
not only on the grid lines, but the <rect> elements that contain the grid lines.
// ready event
$scope.onReady = function (chartWrapper) {
// find, move labels
var labels = chartWrapper.getChart().getContainer().getElementsByTagName('text');
Array.prototype.forEach.call(labels, function(label) {
if (label.getAttribute('text-anchor') === 'end') {
var yLabel = parseFloat(label.getAttribute('y')) + (parseFloat(label.getAttribute('font-size')) * 2);
label.setAttribute('y', yLabel);
}
});
// find, expand grid lines
var gridLines = chartWrapper.getChart().getContainer().getElementsByTagName('rect');
Array.prototype.forEach.call(gridLines, function(line) {
if ((line.getAttribute('height') === '1') ||
((line.getAttribute('x') !== '0') &&
((line.getAttribute('fill') === null) || (line.getAttribute('fill') === '#ffffff')))) {
var lineWidth = parseFloat(line.getAttribute('width')) + parseFloat(line.getAttribute('x')) - 2;
line.setAttribute('x', 2);
line.setAttribute('width', lineWidth);
}
});
}
see following working snippet...
var app = angular.module('mainApp', ['googlechart']);
app.controller('mainSearchController', function ($scope) {
$scope.ShowChart = function () {
var saleChart = {};
saleChart.type = 'BubbleChart';
saleChart.cssStyle = "height:100%; width:100%;";
var options = {
sizeAxis: {
maxSize: 7,
minSize: 1
},
fontSize:10,
legend: 'none',
height: 200,
width: 400,
bubble: { stroke: '#fdca0f', opacity: 1 },
colors: ['#fdca0f', '#fdca0f'],
tooltip: {
trigger: 'none'
},
hAxis: {
ticks: [
{ v: 800, f: '2015' },
{ v: 1200, f: '2016' },
{ v: 1600, f: '2017' },
{ v: 2000, f: '2018' },
{ v: 2400, f: '2019' },
{ v: 2800, f: '2020' }
],
gridlines: { color: '#dedede' },
minorGridlines: { color: '#f7f7f7', count: 3 },
textStyle: { color: '#5f5f5f' }
},
vAxis: {
ticks: [
// add line break --> \n
{ v: 1, f: 'Chennai\nin March' },
{ v: 2, f: 'Mumbai\nin March' },
{ v: 3, f: 'Delhi\nin April' },
{ v: 4, f: 'Chennai\nin April' }
],
gridlines: { color: '#dedede' },
textStyle: { color: '#5f5f5f' }
}
};
var d = [["Name", "Year", "Place", "", "Sales", "tooltip"],
["", 1000, 2, "", 26, "Sale List"],
["",1200,3,"",28,"Sale List"],
["",1400,3,"",48,"S"],["",1600,3,"",29,"S"]];
saleChart.data = d;
$scope.chartData = d;
saleChart.options = options;
$scope.saleChart = saleChart;
}
var mouseX;
var mouseY;
$scope.mousepoints = function (e) {
mouseX = e.pageX;
mouseY = e.pageY;
}
$scope.showTooltip = function (row) {
var x = mouseX;
var y = mouseY + 10;
if (row != null) {
dataTable = google.visualization.arrayToDataTable($scope.chartData);
var v = dataTable.getValue(row, 5);
//var v = $scope.chartData.rows[row][5];
v = v.toString().replace(/,/g, "<br/>")
$('#custom_tooltip').html('<div>' + v + '</div>').css({
'top': y,
'left': x
}).fadeIn('slow');
}
}
$scope.hideTooltip = function () {
$('#custom_tooltip').fadeOut('fast');
}
$scope.onReady = function (chartWrapper) {
var labels = chartWrapper.getChart().getContainer().getElementsByTagName('text');
var labelIndex = 0;
var nextLabels = [];
Array.prototype.forEach.call(labels, function(label) {
// find label
if (label.getAttribute('text-anchor') === 'end') {
// move label down
var yLabel = parseFloat(label.getAttribute('y')) + (parseFloat(label.getAttribute('font-size')) * 1.5);
label.setAttribute('y', yLabel);
// set text line 1
var labelText = chartWrapper.getOption('vAxis.ticks')[labelIndex].f.split('\n');
label.textContent = labelText[0].toUpperCase();
// save label
nextLabels.push(label);
labelIndex++;
}
});
// add line 2
nextLabels.forEach(function (label, labelIndex) {
var yLabel = parseFloat(label.getAttribute('y')) + (parseFloat(label.getAttribute('font-size')) + 1);
var nextLabel = label.parentNode.appendChild(label.cloneNode(true));
var labelText = chartWrapper.getOption('vAxis.ticks')[labelIndex].f.split('\n');
nextLabel.textContent = labelText[1];
nextLabel.setAttribute('y', yLabel);
// increase font size of line 1
label.setAttribute('font-size', (parseFloat(label.getAttribute('font-size')) + 1));
// re-align labels to left
var labelWidth = label.getBBox().width;
label.setAttribute('x', labelWidth + 2);
labelWidth = nextLabel.getBBox().width;
nextLabel.setAttribute('x', labelWidth + 2);
});
var gridLines = chartWrapper.getChart().getContainer().getElementsByTagName('rect');
Array.prototype.forEach.call(gridLines, function(line) {
if ((line.getAttribute('height') === '1') ||
((line.getAttribute('x') !== '0') &&
((line.getAttribute('fill') === null) || (line.getAttribute('fill') === '#ffffff')))) {
var lineWidth = parseFloat(line.getAttribute('width')) + parseFloat(line.getAttribute('x')) - 2;
line.setAttribute('x', 2);
line.setAttribute('width', lineWidth);
}
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.8/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-google-chart/0.1.0/ng-google-chart.min.js"></script>
<div data-ng-app="mainApp" data-ng-controller="mainSearchController" ng-init="ShowChart()">
<div class="row" ng-mouseover="mousepoints($event)">
<div google-chart chart="saleChart" agc-on-mouseover="showTooltip(row)" agc-on-mouseout="hideTooltip()" agc-on-ready="onReady(chartWrapper)"></div>
<div id="custom_tooltip" style="position:fixed; border:0px solid #777777; padding-left:10px; line-height:15px; color:#5f5f5f; font-family:Arial; background-color:#FFFFFF; height:auto; width:auto; font-size:10px;"></div>
</div>
</div>
google charts uses clip-path to attach visual graphics. You can take control over this using cx (horizontal axis), cy (vertical axis), and r (radius) attributes on <circle> elements.
As each row will (or must) have same height you can simply add some js to make cy = cy - 10; (or whatever number that makes appear your circle where you want.
BUT there's another issue here, you can't set circles over the top axis, or bottom. Well, you can but it will be half-out of the canvas. At this point i think there's not much to do here, can't use z-index css property on this elements so you may not being able to reach the desired approach.
I am using jqxscheduler. I want to enable the dragging of event to whole window
below give code I am using to initialize the calendar what I need to add move to make the event boxes drag-able outside the calendar
$("#scheduler").jqxScheduler({
date: new $.jqx.date(2017, 11, 23),
width: "100%",
height: "100%",
source: adapter,
renderAppointment: function(data) {
set();
var img = "<img style='top: 2px; height: 17px; position: relative;' src='" + data.appointment.location + "'/>";
if (data.view == "weekView" || data.view == "dayView" || data.view == "monthView") {
data.style = data.appointment.desc;
data.style = data.appointment.description;
data.html = img + "<i>" + data.appointment.subject + "</i>";
/*
if (data.appointment.id == "id1") {
data.style = "#AA4643";
}
else if (data.appointment.id == "id2" || data.appointment.id == "id6") {
data.style = "#309B46";
}
else if (data.appointment.id == "id3") {
data.style = "#447F6E";
}*/
}
return data;
},
ready: function() {
$("#scheduler").jqxScheduler('ensureAppointmentVisible', 'id-11');
},
appointmentDataFields: {
from: "start",
to: "end",
id: "id",
description: "description",
location: "location",
subject: "subject",
style: "style",
color: "color",
background: "background",
borderColor: "borderColor"
},
view: 'weekView',
appointmentsMinHeight: 20,
width: '100%',
height: '100%',
views: [{
type: "dayView",
showWeekends: false,
timeRuler: {
scale: "quarterHour"
}
},
{
type: "weekView",
showWeekends: false,
timeRuler: {
scale: "quarterHour"
}
},
{
type: "monthView",
showWeekends: false,
timeRuler: {
scale: "quarterHour"
}
},
]
});
Please check the output of above defined code
I have a pie chart integration in Spotfire which works well when the data is in a simpler format in 'data' and 'columns'. The data binds to the chart properly (this is the kind of format i've seen in most demos).
However in other real-life usages in Spotfire, the JSON which is produced is differently formatted and ceases to draw a pie chart properly. I think it should be possible to adjust the script to bind to this data format, but i don't know how?
In my fiddle it is working with simpler data format, if commenting this out and uncommenting the other data the failed chart can be seen...
https://jsfiddle.net/paulsmithleadershipfactor/3k2gzuw0/
The full code is here also...
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9"/>
<title>JS Visualization Tester with Highcharts</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script>
var chart; // Global chart object used to determine whether Highcharts has been intialized
var color = null;
var pie = null;
var svg = null;
var path = null;
function renderCore(sfdata)
{
if (resizing) {
return;
}
// Extract the columns
var columns = sfdata.columns;
columns.shift();
// Extract the data array section
var chartdata = sfdata.data;
// count the marked rows in the data set, needed later for marking rendering logic
var markedRows = 0;
for (var i = 0; i < chartdata.length; i++)
{
if (chartdata[i].hints.marked)
{
markedRows = markedRows + 1;
}
}
var width = window.innerWidth;
var height = window.innerHeight;
var radius = Math.min(width, height) / 2;
if ( !chart )
{
$('#js_chart').highcharts({
chart: {
plotBackgroundColor: '#f1f2f2',
plotBorderWidth: null,
plotShadow: false,
type: 'pie'
},
title: {
text: 'Pie',
},
tooltip: {
formatter: function() {
var sliceIndex = this.point.index;
var sliceName = this.series.chart.axes[0].categories[sliceIndex];
return sliceName + ':' +
'<b>' + this.y + '</b>';
}
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
showInLegend: true,
depth: 35,
innerSize: 100,
dataLabels: {
enabled: true,
format: '{point.y:,.0f}'
}
}
},
legend: {
enabled: true,
labelFormatter: function() {
var legendIndex = this.index;
var legendName = this.series.chart.axes[0].categories[legendIndex];
return legendName;
}
},
xAxis: {
categories: columns
}
});
}
chart = $('#js_chart').highcharts();
for ( var nIndex = 0 ; nIndex < chartdata.length ; nIndex++ )
{
var row = chartdata[nIndex];
// Check for an existing chart data series with the current id
var series = chart.get ( row.items[0] );
var seriesData = [];
for (var c = 1; c < row.items.length; c++) {
seriesData.push(Number(row.items[c]));
}
if ( series != null )
{
// Update the existing series with the new data
series.update ( {
data: seriesData
}, false );
}
else
{
// Create a new series
chart.addSeries ( {
id: row.items[0],
name: row.items[0],
data: seriesData
}, false );
}
}
for ( nSeriesIndex = 0 ; nSeriesIndex < chart.series.length ; nSeriesIndex++ )
{
var series = chart.series[nSeriesIndex];
var found = false;
for ( nDataIndex = 0 ; nDataIndex < chartdata.length ; nDataIndex++ )
{
var row = chartdata[nDataIndex];
if ( series.name == row.items[0] )
{
found = true;
break;
}
}
if ( found != true )
{
series.remove ( false );
nSeriesIndex = 0;
}
}
chart.redraw ();
wait ( sfdata.wait, sfdata.static );
}
var resizing = false;
window.onresize = function (event) {
resizing = true;
if ($("#js_chart")) {
}
resizing = false;
}
</script>
</head>
<body>
<button style="position:absolute; z-index:99" type="button" onclick="call_renderCore()">Call renderCore</button>
<div id="js_chart"></div>
<script type="text/javascript">
function call_renderCore()
{
var sfdata =
{
"columns": ["Sales (Total)", "Marketing (Total)", "Development (Total)", "Customer Support (Total)", "IT (Total)", "Administration (Total)"],
/* comment out the 'columns' above and uncomment 'columns' below */
/* "columns": [
"count([lastcontact])",
"First([lastcontact])"
], */
/* uncomment above and comment below */
"data": [{"items": [93000, 58000, 102000, 66000, 43000, 24000], "hints": {"index": 0}}]
/* comment out the 'data' above and uncomment 'data' below */
/* "data": [
{
"items": [
131,
"3 – 6 months"
],
"hints": {
"index": 0
}
},
{
"items": [
78,
"6 months – 1 year"
],
"hints": {
"index": 1
}
},
{
"items": [
89,
"Can't remember"
],
"hints": {
"index": 2
}
},
{
"items": [
56,
"Over a year ago"
],
"hints": {
"index": 4
}
},
{
"items": [
442,
"Less than 3 months"
],
"hints": {
"index": 3
}
}
], */
}
renderCore ( sfdata );
display_data ( sfdata );
}
</script>
</body>
</html>
I'm trying to make an Excel-like editor with HandsOnTable but I haven't yet figured out how to change a cell's style dynamically, borders in this case.
I have tried to use
setCellMeta(row,col,"borders", My_borders_Object);
and then
MyHotInstance.render();
but this had no effect.
What could I do to solve this problem?
Any help will be very appreciated.
Not sure what my_borders_object is or why you're passing "borders" as a cell meta data argument, but here's a good way of doing it:
There is an initialization option called customBorders; see below for excerpt from documentation:
customBorders : Boolean (default false)
customBorders : Array [{ row: 2, col: 2, left: {width:2, color: 'red'}, right: {width:1, color: 'green'}, top: /*...*/, bottom: /*...*/ }]
customBorders : Array [{ range:{ from:{ row: 1, col: 1 }, to:{ row: 3, col: 4 } }, left: { /*...*/ }, right: { /*...*/ }, top: { /*...*/ }, bottom: { /*...*/ } }]
If true, enables Custom Borders plugin, which enables applying custom borders through the context menu (configurable with context menu key borders).
To initialize Handsontable with predefined custom borders, provide cell coordinates and border styles in form of an array.
See Custom Borders demo for examples.
Version added: 0.11.0
What this means is that at any given point, if you wanted to do a dynamic update of borders, you can use
hotInstance.updateSettings({
customBorders: new_borders_array
})
I'm trying to accomplish the same right now actually. I have tried the following:
ht is the handsontable instance
ht.updateSettings({
customBorders: [
{ range:
{
from: { row: 1, col: 15 },
to: { row: 1, col: 16 }
},
top: { width: 3, color: 'red' },
left: { width: 2, color: 'red' },
bottom: { width: 2, color: 'red' },
right: { width: 2, color: 'red' }
},
]
});
Without ht.init() it does not work:
ht.init();
In version 0.17 this worked fine, however after the update in version 0.18 ht.init(); it creates another instance of the table below the current one - very frustrating.
So now I'm again stuck, or I will downgrade to 0.17 until this is fixed in 0.18.
After going thought the handsontable.full.js I managed to do it by extracting some function from the code and building the borders objects:
var container = document.getElementById('ht_container');
var data = function () {
return Handsontable.helper.createSpreadsheetData(20, 12);
};
var hot = new Handsontable(container, {
data: data(),
height: 396,
colHeaders: true,
rowHeaders: true,
stretchH: 'all',
customBorders: true,
});
//get handsontable instance
var instance = hot;
//copy required functions from the JS.... not pretty, but easy enough
//instead of building the required objects manually
var getSettingIndex = function(className) {
for (var i = 0; i < instance.view.wt.selections.length; i++) {
if (instance.view.wt.selections[i].settings.className == className) {
return i;
}
}
return -1;
};
var insertBorderIntoSettings = function(border) {
var coordinates = {
row: border.row,
col: border.col
};
var selection = new WalkontableSelection(border, new WalkontableCellRange(coordinates, coordinates, coordinates));
var index = getSettingIndex(border.className);
if (index >= 0) {
instance.view.wt.selections[index] = selection;
} else {
instance.view.wt.selections.push(selection);
}
};
var createClassName = function(row, col) {
return "border_row" + row + "col" + col;
};
var createDefaultCustomBorder = function() {
return {
width: 1,
color: '#000'
};
};
var createSingleEmptyBorder = function() {
return {hide: true};
};
var createDefaultHtBorder = function() {
return {
width: 1,
color: '#000',
cornerVisible: false
};
};
var createEmptyBorders = function(row, col) {
return {
className: createClassName(row, col),
border: createDefaultHtBorder(),
row: row,
col: col,
top: createSingleEmptyBorder(),
right: createSingleEmptyBorder(),
bottom: createSingleEmptyBorder(),
left: createSingleEmptyBorder()
};
};
var prepareBorderFromCustomAddedRange = function(rowObj) {
var range = rowObj.range;
for (var row = range.from.row; row <= range.to.row; row++) {
for (var col = range.from.col; col <= range.to.col; col++) {
var border = createEmptyBorders(row, col);
var add = 0;
if (row == range.from.row) {
add++;
if (rowObj.hasOwnProperty('top')) {
border.top = rowObj.top;
}
}
if (row == range.to.row) {
add++;
if (rowObj.hasOwnProperty('bottom')) {
border.bottom = rowObj.bottom;
}
}
if (col == range.from.col) {
add++;
if (rowObj.hasOwnProperty('left')) {
border.left = rowObj.left;
}
}
if (col == range.to.col) {
add++;
if (rowObj.hasOwnProperty('right')) {
border.right = rowObj.right;
}
}
if (add > 0) {
this.setCellMeta(row, col, 'borders', border);
insertBorderIntoSettings(border);
}
}
}
};
$(document).ready(function () {
//create my borders object
var customBorders = [
{ range:
{
from: { row: 1, col: 2 },
to: { row: 4, col: 4 }
},
top: { width: 3, color: 'red' },
left: { width: 2, color: 'red' },
bottom: { width: 2, color: 'red' },
right: { width: 2, color: 'red' }
},
];
//used the 'stolen' functions to add them to the HT in
prepareBorderFromCustomAddedRange.call(instance, customBorders[0]);
instance.render();
instance.view.wt.draw(true);
instance.customBorders = customBorders;
});
</style>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<link href="http://handsontable.com//styles/main.css" rel="stylesheet">
<link href="http://handsontable.com//bower_components/handsontable/dist/handsontable.full.min.css" rel="stylesheet">
<script src="http://handsontable.com//bower_components/handsontable/dist/handsontable.full.min.js"></script>
<style type="text/css">
body {background: white; margin: 20px;}
h2 {margin: 20px 0;}
<div id="ht_container"></div>
But if you are not lazy, you can build pretty much build your 'border' object and use, insertBorderIntoSettings to add them to your table or write custom code that does the same.