I am making charts using c3.js. I have to make the contents of the tooltip cilckable. Till now, the tooltip is visible only when i hover over the chart. I have some Information which is to be displayed when i click on a link in the tooltip. I couldn't find any help from c3 documentation. Snippet of the code i am working on is shown below.
$scope.timelineConfig.tooltip.contents = function (data, defaultTitleFormat, defaultValueFormat, color) {
var $$ = this, config = $$.config,
titleFormat = config.tooltip_format_title || defaultTitleFormat,
nameFormat = config.tooltip_format_name || function (name) { return name; },
valueFormat = config.tooltip_format_value || defaultValueFormat,
text, i, title, value;
text = "<div id='tooltip' class='d3-tip'>";
title = dates[data[0].index];
text += "<span class='info'><b><u>Date</u></b></span><br>";
text += "<span class='info'>"+ title +"</span><br>";
text += "<span class='info'><b><u>Features</u> : </b> " + features[data[0].index] + "</span><br>";
text += "<span class='info'><b><u>Enhancements</u> : </b> " + defects[data[0].index] + "</span><br>";
text += "</div>";
return text;
};
I have to make the contents (<span><b><u>Features...</u></b></span>) clickable.
First (if you haven't already done so) override the tooltip position so that it doesn't keep running away when you try to click it.
tooltip: {
position: function () {
var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
position.top = 0;
return position;
},
Then you need to override the hideTooltip function so that it doesn't close before your click event can be detected.
var originalHideTooltip = chart.internal.hideTooltip
chart.internal.hideTooltip = function () {
setTimeout(originalHideTooltip, 100)
};
Then, you just need to override the pointer-events style (so that the mouse events are not ignored) and then attach the handler as you normally would in jQuery
$(".c3-tooltip-container")
.css("pointer-events", "auto")
.on('click', '.info:eq(2)', function () {
// add click functionality here. you could pass in additional data using the span attributes
alert($(this).text())
})
Modify the selector as required (like adding the chart wrapper id...)
Fiddle - http://jsfiddle.net/5vbeb4k8/
I know I'm commenting on an old question, but just for reference in case anyone else needs it, I modified the above answer to work for my code.
In my CSS:
.c3-tooltip-container {
pointer-events: auto !important;
}
In JS:
c3.chart.internal.fn.hideTooltip = function () {
setTimeout(c3.chart.internal.fn.hideTooltip, 100);
};
The position code seems to be optional. But the fixed top is probably more user-friendly.
tooltip: {
position: function () {
var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
position.top = 0;
return position;
},
Thanks #potatopeelings for getting me started with this -- it was a huge help.
Related
I have a d3 area chart with a tooltip that displays the same text in two different divs. The first div, .tooltip.headline.record, displays the selected value in bold. Another div class, .record-label, displays the all of the values at a given point on the x-axis — for both the selected and non-selected paths. Here's a Plunker of the problem.
To illustrate, it currently looks like this:
I've been trying to achieve a result like this:
... or like this:
I've tried the following methods of hiding or removing the duplicative .record-label div, without success — and without error messages to assist in further diagnosis.
function getRecordContent(obj, pos) {
if ( $(".tooltip-headline-record").text() == $(".record-label").text() ) {
$(".record-label").hide();
//$(".record-label").remove();
//console.log("same");
}
return '<li><div class="record-label">' + obj.state + " " + obj.record.toLowerCase() + " " + numFormat(obj.values[pos].y) + '</div></li>'
}
Here, again, is a Plunker that demonstrates the problem I'm trying to solve (see, specifically, the code beginning at line 480:
http://plnkr.co/edit/NfMeTpXzXGTxgNFKPFJe?p=preview
Is this what you're looking for?
Plunkr
Relevant code changes:
The whole dataset was being passed to the getRecordContent function. So I changed that: when hovered over "admissions", pass "transfers" and "codependents". (line: 435)
var filtered_dataset = dataset.filter(function(row){return row.record !== d.record; });
for (var i = 0; i < filtered_dataset.length; i++) {
content += getRecordContent(filtered_dataset[i], idx);
}
Seems like you need to specify the state name as well along with the record. (line 480)
return '<li><span class="record-label">' + obj.state + ' ' + obj.record.toLowerCase() + '</span><span class="record-value">' + numFormat(obj.values[pos].y) + '</span></li>'
Edit:
Changes made for the tooltip to adapt to the main chart as well:
var filtered_dataset = dataset.filter(function(row){return row.record !== d.record && row.state === d.state; });
Changed z-index for the tooltip in main.css (try removing it and hovering close to the jquery slider)
z-index: 2;
Hope this helps. :)
JS only. On mouseover I'm calling a function I made that creates a div element with an image inside.
I pass (this) as a parameter to the function. The function works and onmouseover it creates a child element and I can click it. However, If I add on mouse out of the div to remove itself, it will only do so if I hovered over it. If I didn't, the div stays and on next hover it adds another one. If I add on mouse out of the parent element to remove the div, I cannot get to hover over the child div, cause as soon as I leave the parent, the child div is removed. The parent element is an (a href) inside a "TD" in a table. The code goes like this:
<script type="text/javascript">
function PopPanel(ownerElem) {
var myParent = ownerElem.parentNode;
var popanel = document.createElement("div");
popanel.className = "divPopPanel";
popanel.setAttribute("display", "block")
var phoneimg = document.createElement("img");
phoneimg.src = '/images/ImageAdditions/Phone.png';
phoneimg.className = "popupPhone";
popanel.appendChild(phoneimg);
phoneimg.onclick = function () {
try {
location.replace("Mylauncher:\\\\nas\\vol5\\SYSTEM\\ITR\\Scripts\\SomeProgram.exe" + " " + ownerElem.innerText);
}
catch (err) {
}
};
myParent.appendChild(popanel);
popanel.onmouseout = function (e) { this.parentNode.removeChild(this) }; //this removes itself on mouseout.
myParent.onmouseout = function (e) { popanel.parentNode.removeChild(popanel) }; // this removes the child element of the parent (which is the same element as above) on mouse out.
};
Well, after a long and miserable trial and error session, I've figured this out.
First I've modified the code that generates and populates the gridview with data, like this:
VB.net
dt.Columns.Add("InternalPhoneDialer", Type.GetType("System.String"))
Dim rn As New Random
Dim randNum As Integer = rn.Next(12, 428)
Dim internalphone As String = dr("InternalPhone").ToString
If internalphone.Contains(" ") Then
internalphone = internalphone.Substring(0, internalphone.IndexOf(" "))
internalphone = internalphone & randNum.ToString()
Else
internalphone = internalphone & randNum.ToString()
End If
//Substitute the current column with the newly created one above
dr("InternalPhoneDialer") = "<div id='popPanelWrapper" & internalphone & "' onmouseover='PopPanel(" & "popPanelWrapper" & internalphone & ");' onmouseleave='PopPanelClose(" & "popPanelWrapper" & internalphone & ");'> <a class='popPanelLink' href='javascript:void(0);' >" & dr("InternalPhone") & "</a> </div>"
I have made sure that I concatenate a unique id to each div in case the phone number is the same for another column (where I implement the same solution). So I added the column inner content + a random number and concatenated it to the DIV name.
Then, on client side I've modified my script like this:
JavaScript
<script type="text/javascript">
function PopPanel(ownerElem) {
var myParent = ownerElem;
var phoneimgexist = !!document.getElementById("popupPhone");
if (phoneimgexist) {
return
} else {
var phoneimg = document.createElement("img");
phoneimg.src = '/_layouts/15/images/ImageAdditions/Phone.png';
phoneimg.id = "popupPhone";
phoneimg.setAttribute("display", "block")
myParent.appendChild(phoneimg);
}
phoneimg.onclick = function () {
try {
location.replace("launcher:\\\\drive01\\vol1\\SYSTEM\\ITR\\Scripts\\Jabber.exe" + " " + ownerElem.innerText);
}
catch (err) {
}
};
};
function PopPanelClose(ownerElem) {
var myParent = ownerElem;
var phoneimg = document.getElementById("popupPhone");
var phoneimgexist = !!document.getElementById("popupPhone");
if (phoneimgexist) {
phoneimg.parentNode.removeChild(phoneimg);
} else {
return
}
};
Now, on mouse over a GridVew cell that contains a phone number I get an icon. By clicking it I can call the number.
In my opinion this solution is much better suited for the task, instead of creating a hidden div with image and data for every row in what could be thousands of entries in the GridView.
This no doubt saves a lot of resources.
I have a setup where i am trying to assign a mouseover and mouseout event on a div, but they dont appear to be firing. Right now i just have it trying to console.logthe mouseout event and add classes to the body as well for the mouseover event.
I have trying using .addEventListener('mouseover', function(){}) instead of .onmouseover to no avail. I have also tried mouseenter and mouseleave but these are IE only events.
I need to use pure Javascript rather than any 3rd party library, this also needs to work in IE8+.
HTML
<div class="of_expandable" data-channel="2935725596001" data-muted="1" data-skin="dark"></div>
JS
/*
for each expandable element this function:
- parses the data attributes for handing to the embeds
- styles the expandable div
- binds the hover event to the div
*/
function processExpandable (elm, index){
elm.className += " processed";
elm.id = exp_ns + "_expandable_" + index;
// option parsing
var options = {};
options.skin = elm.getAttribute("data-skin");
if(!options.skin){
options.skin = 'light';
}
// styling
elm.style.width = "300px";
elm.style.height = "250px";
elm.style.position = "relative";
elm.style.backgroundColor = "#000";
// add events to elm
elm.onmouseover = function (){
launchModal(elm, options.skin);
};
elm.onmouseout = function (){
console.log('mouseout');
};
}
/*
opens the modal and populates
*/
function launchModal (elm, skin){
console.log('entered');
document.body.className += " " + exp_ns + "_modal_open modal_skin_" + skin;
}
/*
closes the modal and clears
*/
function closeModal (){
// TODO: clear data from modal
var pattern = "/\b" + exp_ns + "_modal_open\b/";
document.body.className = document.body.className.replace(pattern,'');
var pattern = "/\bmodal_skin_light\b/";
document.body.className = document.body.className.replace(pattern,'');
var pattern = "/\bmodal_skin_dark\b/";
document.body.className = document.body.className.replace(pattern,'');
}
/*
adds the modal element waiting to be triggered by the expandables
- adds background
- adds modal box
*/
function addModal (){
var modalHTML = '<div id="' + exp_ns + '_modal" class="exp_modal"></div><div id="' + exp_ns + '_modal_back" class="exp_modal_back"></div>';
document.body.innerHTML += modalHTML;
}
/*
tests if an element has a class
*/
function hasClass(elm, cls) {
return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
var exp_ns = "of"
, expandables = getElementsByClassName(exp_ns + '_expandable')
, hoverTimer
, hoverPause = 1000;
if(expandables.length > 0){
for (var i = 0; i < expandables.length; i++){
if(!hasClass(expandables[i], "processed")){
// make sure we arent adding twice from a double include
processExpandable(expandables[i], i);
}
}
// make sure we arent adding twice from a double include
var modal = document.getElementById(exp_ns + '_overlay');
if(!modal){
addModal();
}
}else{
console.log('warning: no expandable elements found');
}
here's a JSFiddle
SOLUTION UPDATE:
So it appears that the reason this was breaking was because of the way that I was inserting the modal elements using document.body.innerHTML += . Which I think must read all the innerHTML with the newly appended content. As a better solution I used this:
function addModal (){
var modalBack = document.createElement("div");
modalBack.setAttribute("class", "exp_modal_back");
document.getElementsByTagName("body")[0].appendChild(modalBack);
var modalCont = document.createElement("div");
modalCont.setAttribute("class", "exp_modal");
document.getElementsByTagName("body")[0].appendChild(modalCont);
}
updated JSFiddle
Your problem is not with how you are using the event handlers. The problem is being caused by the "addModal" function called after the "processExpandable" function. I don't understand what you are trying to accomplish so I can't help you there, but it is a start.
Also, I think you have a problem in the "launchModal" function. Do you really want to keep adding and adding values to the class attribute of the body?
You can try this way :
<div style=" height: 200px; width:200px; background-color: black;"
onmouseover="displayElement('myDiv');"
onmouseout="hideElement('myDiv');">
<div id="myDiv" class="of_expandable" data-channel="2935725596001" data-muted="1" data-skin="dark" style="width:100%; height:100%;display: none; background-color:green;">Content</div>
</div>
here is a JSBin to show you
I'm using html2canvas to render html contents to image. But it supports only single blank space between word and also all text displayed only in One.
Example 1
if text is `Word1 Word2` it become to `word1 word2`
Example 2
This is First line
This is Second Line
Image:
THis is First line This is Second Line
I looked in to the html2canvas Code and I believe below these two functions are responsible for drawing the text and spaces. Help me how can i achieve my target.
function renderText(el, textNode, stack) {
var ctx = stack.ctx,
color = getCSS(el, "color"),
textDecoration = getCSS(el, "textDecoration"),
textAlign = getCSS(el, "textAlign"),
metrics,
textList,
state = {
node: textNode,
textOffset: 0
};
if (Util.trimText(textNode.nodeValue).length > 0) {
textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
textNode.nodeValue.split(/(\b| )/)
: textNode.nodeValue.split("");
metrics = setTextVariables(ctx, el, textDecoration, color);
if (options.chinese) {
textList.forEach(function(word, index) {
if (/.*[\u4E00-\u9FA5].*$/.test(word)) {
word = word.split("");
word.unshift(index, 1);
textList.splice.apply(textList, word);
}
});
}
textList.forEach(function(text, index) {
var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
if (bounds) {
drawText(text, bounds.left, bounds.bottom, ctx);
renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
}
});
}
}
function drawText(currentText, x, y, ctx){
if (currentText !== null && Util.trimText(currentText).length > 0) {
ctx.fillText(currentText, x, y);
numDraws+=1;
}
}
html2canvas render the textarea or Input box value in the one line and trim all more than one spaces between words. So I found solution by converting text area into the div tag , Check out html contenteditable Attribute
Replace <textarea></textarea> with <div contenteditable="true"></div>
if you want to have the same textarea behavior to the div with jquery then use this code
$( '#EDITABLE' ).focus();
var selection = window.getSelection();
var range = document.createRange();
var div = $('#div2').get(0);
range.setStartBefore(div);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
// cursor should now be between div1 and div2
range = window.getSelection().getRangeAt(0);
console.log("range object returned is: ", range);
See Example: http://jsfiddle.net/9ZZpX/3/
I know it's old but, here is the workaround I came with using jquery/JS for your "Example 2":
var oldTextArea = $('#textArea').replaceWith("<div id='divForTA' class='divTextArea'>" + $('#textArea').val().replace(/\n/g, "<br>") + "</div>");
var el = document.querySelector("#container");
html2canvas(el).then(canvas => {
canvas.toDataURL();
$('#divForTA').replaceWith(oldTextArea);
});
So basicaly you replace your textArea with a "div" and once the canvas is rendered as image you revert the new created "div" to "textArea"
You can style the "div" using the "id" to add a border to make it looks like "textarea" like this:
#divForTA {
border: solid 1px lightgrey;
}
I suggest to put a comment in the github html2canvas issue so at some point this will be fixed
https://github.com/niklasvh/html2canvas/issues/2008
Hope this will help someone :-)
I tried all kinds of things, including a parameter that exists in a specific version of html2canvas which is: letterRendering: true in the options of your object.
In my case, I receive an error that this option doesn't exists in the lib that I've download
html2canvas(document.querySelector("#capture2image"), {
allowTaint: true,
useCORS: true,
logging: false
})
So i just did a simple/non-maintainable thing:
{{ name.replace(" ", " ") }}
maybe it will help others who also tried everything yet nothing worked ....
there is a race condition in html2canvas.
try this:
setTimeout(() => {
canvas = html2canvas(element, {scale:1});
}, 0)
I have created a HTML table which has a function written in javascript that takes the value of the cursors position within a large table cell and then prints the value into a cell.
How would i go about printing the value in a tooltip instead of a table cell?
The simplest thing to do is to set the "title" attribute of some element.
element.title = "Something to show in a tooltip";
There are fancy Javascript tools to make fancy tooltips. You don't say where you want the tooltip, so it's not 100% clear what you're trying to accomplish.
you can go showing the cell content in a tooltip by something like this
jQuery.fn.applyTooltip = function(options) {
var settings = $.extend({}, options);
return this.each(function() {
var ReqElemPosition = $(this).offset();
$(this).hover(function() {
$('body').append('<div class="tooltip"></div>');
tooltip = $('.tooltip');
tooltip.empty().append($(this).text());
tooltip.fadeIn('fast').css({
top: ReqElemPosition.top + tooltip.height() * 2 - $('body').offset().top,
left: ReqElemPosition.left - ($('body').offset().left - $(this).offset().left)
});
}, function() {
tooltip.fadeOut('fast', function() {
$(this).remove();
});
});
});
};
$(document).ready(function() {
$('td').applyTooltip();
});