jsPDF multi page PDF with HTML renderer - multipage

I am using jsPDF in my site to generate PDFs. But now I have multiple DIVs to print in a single PDF. which may take 2 to 3 pages.
For example:
<div id="part1">
content
</div>
<div id="part2">
content
</div>
<div id="part2">
content
</div>
my JS code
This works but not as I expected, It add a part of the content(which cannot be included in more than one page).
It removes html tags like br, h1 etc.
function formtoPDF() {
jsPDF.API.mymethod = function() {
// 'this' will be ref to internal API object. see jsPDF source
// , so you can refer to built-in methods like so:
// this.line(....)
// this.text(....)
};
var doc = new jsPDF();
doc.mymethod();
var pdfPart1 = jQuery('#genPDFpart1');
var pdfPart2 = jQuery(".ltinerary");
var pdfPart3 = jQuery("#domElementHTML");
var specialElementHandlers = {
'#loadVar': function(element, renderer) {
return true;
}
};
doc.fromHTML(pdfPart1.html() + pdfPart3.html() + pdfPart3.html(), 15, 15, {
'width': 170,
'elementHandlers': specialElementHandlers
});
doc.output('save', 'Download.pdf');
}
What's the solution for this?

I have the same working issue. Searching in MrRio github I found this: https://github.com/MrRio/jsPDF/issues/101
Basically, you have to check the actual page size always before adding new content
doc = new jsPdf();
...
pageHeight= doc.internal.pageSize.height;
// Before adding new content
y = 500 // Height position of new content
if (y >= pageHeight)
{
doc.addPage();
y = 0 // Restart height position
}
doc.text(x, y, "value");

here's an example using html2canvas & jspdf, although how you generate the canvas doesn't matter--we're just going to use the height of that as the breakpoint on a for loop, in which a new page is created and content added to it.
after the for loop, the pdf is saved.
function makePDF() {
var quotes = document.getElementById('container-fluid');
html2canvas(quotes).then((canvas) => {
//! MAKE YOUR PDF
var pdf = new jsPDF('p', 'pt', 'letter');
for (var i = 0; i <= quotes.clientHeight/980; i++) {
//! This is all just html2canvas stuff
var srcImg = canvas;
var sX = 0;
var sY = 980*i; // start 980 pixels down for every new page
var sWidth = 900;
var sHeight = 980;
var dX = 0;
var dY = 0;
var dWidth = 900;
var dHeight = 980;
window.onePageCanvas = document.createElement("canvas");
onePageCanvas.setAttribute('width', 900);
onePageCanvas.setAttribute('height', 980);
var ctx = onePageCanvas.getContext('2d');
// details on this usage of this function:
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images#Slicing
ctx.drawImage(srcImg,sX,sY,sWidth,sHeight,dX,dY,dWidth,dHeight);
// document.body.appendChild(canvas);
var canvasDataURL = onePageCanvas.toDataURL("image/png", 1.0);
var width = onePageCanvas.width;
var height = onePageCanvas.clientHeight;
//! If we're on anything other than the first page,
// add another page
if (i > 0) {
pdf.addPage(612, 791); //8.5" x 11" in pts (in*72)
}
//! now we declare that we're working on that page
pdf.setPage(i+1);
//! now we add content to that page!
pdf.addImage(canvasDataURL, 'PNG', 20, 40, (width*.62), (height*.62));
}
//! after the for loop is finished running, we save the pdf.
pdf.save('Test.pdf');
});
}

I found the solution on this page: https://github.com/MrRio/jsPDF/issues/434
From the user: wangzhixuan
I copy the solution here:
// suppose your picture is already in a canvas
var imgData = canvas.toDataURL('image/png');
/*
Here are the numbers (paper width and height) that I found to work.
It still creates a little overlap part between the pages, but good enough for me.
if you can find an official number from jsPDF, use them.
*/
var imgWidth = 210;
var pageHeight = 295;
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var doc = new jsPDF('p', 'mm');
var position = 0;
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
doc.addPage();
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.save( 'file.pdf');

var doc = new jsPDF('p', 'mm');
var imgData = canvas.toDataURL('image/png');
var pageHeight= doc.internal.pageSize.getHeight();
var pageWidth= doc.internal.pageSize.getWidth();
var imgheight = $('divName').height() * 25.4 / 96; //px to mm
var pagecount = Math.ceil(imgheight / pageHeight);
/* add initial page */
doc.addPage('l','mm','a4');
doc.addImage(imgData, 'PNG', 2, 0, pageWidth-4, 0);
/* add extra pages if the div size is larger than a a4 size */
if (pagecount > 0) {
var j = 1;
while (j != pagecount) {
doc.addPage('l','mm','a4');
doc.addImage(imgData, 'PNG', 2, -(j * pageHeight), pageWidth-4, 0);
j++;
}
}

You can use html2canvas plugin and jsPDF both. Process order:
html to png & png to pdf
Example code:
jQuery('#part1').html2canvas({
onrendered: function( canvas ) {
var img1 = canvas.toDataURL('image/png');
}
});
jQuery('#part2').html2canvas({
onrendered: function( canvas ) {
var img2 = canvas.toDataURL('image/png');
}
});
jQuery('#part3').html2canvas({
onrendered: function( canvas ) {
var img3 = canvas.toDataURL('image/png');
}
});
var doc = new jsPDF('p', 'mm');
doc.addImage( img1, 'PNG', 0, 0, 210, 297); // A4 sizes
doc.addImage( img2, 'PNG', 0, 90, 210, 297); // img1 and img2 on first page
doc.addPage();
doc.addImage( img3, 'PNG', 0, 0, 210, 297); // img3 on second page
doc.save("file.pdf");

$( document ).ready(function() {
$('#cmd').click(function() {
var options = {
pagesplit: true //include this in your code
};
var pdf = new jsPDF('p', 'pt', 'a4');
pdf.addHTML($("#pdfContent"), 15, 15, options, function() {
pdf.save('Menu.pdf');
});
});
});

This is my first post which support only a single page http://www.techumber.com/html-to-pdf-conversion-using-javascript/
Now, the second one will support the multiple pages.
http://www.techumber.com/how-to-convert-html-to-pdf-using-javascript-multipage/

Below is my code but the problem is that the document doesn't split to display the other part of the document in a new page.
Please improve this code.
<script type='text/javascript'>
$(document).on("click", "#btnExportToPDF", function () {
var table1 =
tableToJson($('#table1')[0]),
cellWidth =42,
rowCount = 0,
cellContents,
leftMargin = 2,
topMargin = 12,
topMarginTable =5,
headerRowHeight = 13,
rowHeight = 12,
l = {
orientation: 'p',
unit: 'mm',
format: 'a3',
compress: true,
fontSize: 11,
lineHeight: 1,
autoSize: false,
printHeaders: true
};
var doc = new jsPDF(l,'pt', 'letter');
doc.setProperties({
title: 'Test PDF Document',
subject: 'This is the subject',
author: 'author',
keywords: 'generated, javascript, web 2.0, ajax',
creator: 'author'
});
doc.cellInitialize();
$.each(table1, function (i, row)
{
rowCount++;
$.each(row, function (j, cellContent) {
if (rowCount == 1) {
doc.margins = 1;
doc.setFont("Times New Roman");
doc.setFontType("bold");
doc.setFontSize(11);
doc.cell(leftMargin, topMargin, cellWidth, headerRowHeight, cellContent, i)
}
else if (rowCount == 2) {
doc.margins = 1;
doc.setFont("Times ");
doc.setFontType("normal");
// or for normal font type use ------ doc.setFontType("normal");
doc.setFontSize(11);
doc.cell(leftMargin, topMargin, cellWidth, rowHeight, cellContent, i);
}
else {
doc.margins = 1;
doc.setFont("Times ");
doc.setFontType("normal ");
doc.setFontSize(11);
doc.cell(leftMargin, topMargin, cellWidth, rowHeight, cellContent, i);
// 1st=left margin 2nd parameter=top margin, 3rd=row cell width 4th=Row height
}
})
})
doc.save('sample Report.pdf');
});
function tableToJson(table) {
var data = [];
// first row needs to be headers
var headers = [];
for (var i=0; i<table.rows[0].cells.length; i++) {
headers[i] = table.rows[0].cells[i].innerHTML.toLowerCase().replace(/ /gi,'');
}
// go through cells
for (var i=1; i<table.rows.length; i++) {
var tableRow = table.rows[i];
var rowData = {};
for (var j=0; j<tableRow.cells.length; j++) {
rowData[ headers[j] ] = tableRow.cells[j].innerHTML;
}
data.push(rowData);
}
return data;
}
</script>

Automatically not split data to multi pages. You may split manually.
If your ( rowCount * rowHeight ) > 420mm ( A3 Height in mm ) add new page function. ( Sorry I can't edit your code without run )
After add new page leftMargin, topMargin = 0; ( start over )
I added sample code with yours. I hope it's right.
else {
doc.margins = 1;
doc.setFont("Times ");
doc.setFontType("normal ");
doc.setFontSize(11);
if ( rowCount * rowHeight > 420 ) {
doc.addPage();
rowCount = 3; // skip 1 and 2 above
} else {
// now rowcount = 3 ( top of new page for 3 )
// j is your x axis cell index ( j start from 0 on $.each function ) or you can add cellCount like rowCount and replace with
// rowcount is your y axis cell index
left = ( ( j ) * ( cellWidth + leftMargin );
top = ( ( rowcount - 3 ) * ( rowHeight + topMargin );
doc.cell( leftMargin, top, cellWidth, rowHeight, cellContent, i);
// 1st=left margin 2nd parameter=top margin, 3rd=row cell width 4th=Row height
}
}
You can convert html directly to pdf lossless. Youtube video for html => pdf example

html2canvas(element[0], {
onrendered: function (canvas) {
pages = Math.ceil(element[0].clientHeight / 1450);
for (i = 0; i <= pages; i += 1) {
if (i > 0) {
pdf.addPage();
}
srcImg = canvas;
sX = 0;
sY = 1450 * i;
sWidth = 1100;
sHeight = 1450;
dX = 0;
dY = 0;
dWidth = 1100;
dHeight = 1450;
window.onePageCanvas = document.createElement("canvas");
onePageCanvas.setAttribute('width', 1100);
onePageCanvas.setAttribute('height', 1450);
ctx = onePageCanvas.getContext('2d');
ctx.drawImage(srcImg, sX, sY, sWidth, sHeight, dX, dY, dWidth, dHeight);
canvasDataURL = onePageCanvas.toDataURL("image/png");
width = onePageCanvas.width;
height = onePageCanvas.clientHeight;
pdf.setPage(i + 1);
pdf.addImage(canvasDataURL, 'PNG', 35, 30, (width * 0.5), (height * 0.5));
}
pdf.save('testfilename.pdf');
}
});

var a = 0;
var d;
var increment;
for(n in array){
d = a++;
if(n % 6 === 0 && n != 0){
doc.addPage();
a = 1;
d = 0;
}
increment = d == 0 ? 10 : 50;
size = (d * increment) <= 0 ? 10 : d * increment;
doc.text(array[n], 10, size);
}

Related

Get two offset points between two points

Hi I need help finding coordinate or points offset from two endpoints of a line. In my program, I would like to specify the two points and the offset. Then I need to calculate the two offset coordinates.
I worked something out using trigonometry but it only works in some cases and when the line is in the positive quadrant.
Here is an image describing what I need to find:
Points on line
Ok so I need to find X3,Y3 and X4,Y4 coordinates.
My method I followed:
Calculate angle:
Ang = atan((Y2 - Y1)/(X2 - X1))
To find X3:
X3 = X1 + Offset * Cos(Ang)
The same concept for Y3
The issue is that if the line is in a different quadrant the point info is not correct... Any help, please.
This question is a clear case for using 2d vector math. The idea is that we subtract p1 from p2 to give us a vector that describes the length and direction of the line. We then normalize this vector, such that it has a length of 1. If you then multiply this normalized vector with the number of units you'd like to move away from the end and add the result to the end-point, you'll have a new point.
Consider an example walking along the x axis:
p1 = 0,0
p2 = 10,0
dif = p2 - p1 = (10,0)
length is 10, so it's 10 times too long - we divide it by 10 to get a vector 1 unit long.
If we then move 5 times (1,0), we end up at 5,0 - 5 units away, bewdy!
Here's a function that achieves the same thing:
function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
var p1 = new vec2d(x1,y1);
var p2 = new vec2d(x2,y2);
var delta = p2.sub(p1);
var dirVec = delta.clone();
dirVec.normalize();
dirVec.timesEquals(distTowardsP2fromP1);
var resultPoint = p1.add(dirVec);
return resultPoint;
}
As you can see, this makes use of something I've called vec2d. There's a copy of it in the following snippet:
"use strict";
function byId(id){return document.getElemetById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
var end1 = new vec2d(0,0);
var end2 = new vec2d(10,0);
var midPoint = calcOffsetPoint(end1.x,end1.y, end2.x,end2.y, 5);
console.log( midPoint.toStringN(2) );
}
class vec2d
{
constructor(x=0, y=0)
{
this.mX = x;
this.mY = y;
}
get x(){return this.mX;}
set x(newX){this.mX = newX;}
get y(){return this.mY;}
set y(newY){this.mY = newY;}
add(other)
{
return new vec2d(this.x+other.x, this.y+other.y);
}
sub(other)
{
return new vec2d(this.x-other.x, this.y-other.y);
}
timesEquals(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
divByEquals(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
dotProd(other)
{
return this.x*other.x + this.y*other.y;
}
length()
{
return Math.hypot(this.x, this.y);
}
normalize()
{
this.divByEquals( this.length() );
return this;
}
perpendicular()
{
var tmp = this.x;
this.x = -this.y;
this.y = tmp;
return this;
}
clone()
{
return vec2d.clone(this);
}
static clone(other)
{
return new vec2d(other.x, other.y);
}
toString(){return `vec2d {x: ${this.x}, y: ${this.y}}`}
toStringN(n){return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}`}
}
function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
var p1 = new vec2d(x1,y1);
var p2 = new vec2d(x2,y2);
var delta = p2.sub(p1);
var dirVec = delta.clone();
dirVec.normalize();
dirVec.timesEquals(distTowardsP2fromP1);
var resultPoint = p1.add(dirVec);
return resultPoint;
}
I had some spare time over the weekend, so put together a working demo of the image you posted. Have a play around. Make sure you run it in full-screen, so you can see the sliders that set the offsets for p3 and p4. Disregard the coordinate-system transformation stuff, that's just there to allow me to make an image the same dimensions as your image yet conveniently display it in a window with about 5% the area. The questions come from the exercise section of some old text-book I was reading over the weekend.
"use strict";
class vec2d
{
constructor(x=0,y=0)
{
this.x = x;
this.y = y;
}
abs()
{
this.x = Math.abs(this.x);
this.y = Math.abs(this.y);
return this;
}
add(vec1)
{
return new vec2d(this.x+vec1.x, this.y+vec1.y);
}
sub(vec1)
{
return new vec2d(this.x-vec1.x, this.y-vec1.y);
}
mul(scalar)
{
return new vec2d(this.x*scalar, this.y*scalar);
}
plusEquals(vec1)
{
this.x += vec1.x;
this.y += vec1.y;
return this;
}
minusEquals(vec1)
{
this.x -= vec1.x;
this.y -= vec1.y;
return this;
}
timesEquals(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
divByEquals(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
normalize()
{
var len = this.length;
this.x /= len;
this.y /= len;
return this;
}
get length()
{
//return Math.sqrt( (this.x*this.x)+(this.y*this.y) );
return Math.hypot( this.x, this.y );
}
set length(newLen)
{
var invLen = newLen / this.length;
this.timesEquals(invLen);
}
dotProd(vec1)
{
return this.x*vec1.x + this.y*vec1.y;
}
perp()
{
var tmp = this.x;
this.x = -this.y;
this.y = tmp;
return this;
}
wedge(other)
{ // computes an area for parallelograms
return this.x*other.y - this.y*other.x;
}
static clone(other)
{
var result = new vec2d(other.x, other.y);
return result;
}
clone() // clone self
{
return vec2d.clone(this);
}
setTo(other)
{
this.x = other.x;
this.y = other.y;
}
get(){ return {x:this.x, y:this.y}; }
toString(){ return `vec2d {x: ${this.x}, y: ${this.y}}` }
toStringN(n){ return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}` }
print(){console.log(this.toString())}
};
class mat3
{
static clone(other)
{
var result = new mat3();
other.elems.forEach(
function(el, index, collection)
{
result.elems[index] = el;
}
);
return result;
}
clone()
{
return mat3.clone(this);
}
constructor(a,b,c,d,e,f)
{
if (arguments.length < 6)
this.setIdentity();
else
this.elems = [a,b,0,c,d,0,e,f,1];
}
setIdentity()
{
this.elems = [1,0,0, 0,1,0, 0,0,1];
}
multiply(other, shouldPrepend)
{
var a, b, c = new mat3();
if (shouldPrepend === true)
{
a = other;
b = this;
}
else
{
a = this;
b = other;
}
c.elems[0] = a.elems[0]*b.elems[0] + a.elems[1]*b.elems[3] + a.elems[2]*b.elems[6];
c.elems[1] = a.elems[0]*b.elems[1] + a.elems[1]*b.elems[4] + a.elems[2]*b.elems[7];
c.elems[2] = a.elems[0]*b.elems[2] + a.elems[1]*b.elems[5] + a.elems[2]*b.elems[8];
// row 1
c.elems[3] = a.elems[3]*b.elems[0] + a.elems[4]*b.elems[3] + a.elems[5]*b.elems[6];
c.elems[4] = a.elems[3]*b.elems[1] + a.elems[4]*b.elems[4] + a.elems[5]*b.elems[7];
c.elems[5] = a.elems[3]*b.elems[2] + a.elems[4]*b.elems[5] + a.elems[5]*b.elems[8];
// row 2
c.elems[6] = a.elems[6]*b.elems[0] + a.elems[7]*b.elems[3] + a.elems[8]*b.elems[6];
c.elems[7] = a.elems[6]*b.elems[1] + a.elems[7]*b.elems[4] + a.elems[8]*b.elems[7];
c.elems[8] = a.elems[6]*b.elems[2] + a.elems[7]*b.elems[5] + a.elems[8]*b.elems[8];
for (var i=0; i<9; i++)
this.elems[i] = c.elems[i];
}
transformVec2s(pointList)
{
var i, n = pointList.length;
for (i=0; i<n; i++)
{
var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
pointList[i].x = x;
pointList[i].y = y;
}
}
makeTransformedPoints(pointList)
{
var result = [];
for (var i=0,n=pointList.length;i<n;i++)
{
var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
result.push( new vec2d(x,y) );
}
return result;
}
rotate(degrees, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = Math.cos( degrees/180.0 * Math.PI );
tmp.elems[1] = -Math.sin( degrees/180.0 * Math.PI );
tmp.elems[3] = -tmp.elems[1];
tmp.elems[4] = tmp.elems[0];
this.multiply(tmp, shouldPrepend);
}
scaleEach(scaleX, scaleY, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = scaleX;
tmp.elems[4] = scaleY;
this.multiply(tmp, shouldPrepend);
}
scaleBoth(scaleAmount, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = scaleAmount;
tmp.elems[4] = scaleAmount;
this.multiply(tmp, shouldPrepend);
}
translate(transX, transY, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[6] = transX;
tmp.elems[7] = transY;
this.multiply(tmp, shouldPrepend);
}
determinant()
{
var result, a, b;
a = ( (this.elems[0]*this.elems[4]*this.elems[8])
+ (this.elems[1]*this.elems[5]*this.elems[6])
+ (this.elems[2]*this.elems[3]*this.elems[7]) );
b = ( (this.elems[2]*this.elems[4]+this.elems[6])
+ (this.elems[1]*this.elems[3]+this.elems[8])
+ (this.elems[0]*this.elems[5]+this.elems[7]) );
result = a - b;
return result;
}
isInvertible()
{
return (this.determinant() != 0);
}
invert()
{
var det = this.determinant();
if (det == 0)
return;
var a,b,c,d,e,f,g,h,i;
a = this.elems[0]; b = this.elems[1]; c = this.elems[2];
d = this.elems[3]; e = this.elems[4]; f = this.elems[5];
g = this.elems[6]; h = this.elems[7]; i = this.elems[8];
this.elems[0] = (e*i - f*h); this.elems[1] = -((b*i) - (c*h)); this.elems[2] = (b*f)-(c*e);
this.elems[3] = -(d*i - f*g); this.elems[4] = (a*i) - (c*g); this.elems[5] = -( (a*f) - (c*d) );
this.elems[6] = (d*h - e*g); this.elems[7] = -((a*h) - (b*g)); this.elems[8] = (a*e)-(b*d);
var detInv = 1.0 / det;
for (var i=0; i<9; i++)
this.elems[i] *= detInv;
return this;
}
reset()
{
this.setIdentity();
}
print()
{
var str = '';
for (var i=0; i<9; i++)
{
if (i && i%3==0)
str += "\n";
str += " " + this.elems[i].toFixed(5);
}
console.log(str);
}
}
function byId(id){return document.getElementById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
byId('output').addEventListener('mousemove', onMouseMove, false);
byId('slider1').addEventListener('input', onSliderInput, false);
byId('slider2').addEventListener('input', onSliderInput, false);
draw();
}
//(400-48)/400 = 0.88
var invMat, svgInvMat;
function onMouseMove(evt)
{
var mousePos = new vec2d(evt.offsetX,evt.offsetY);
var worldPos = mousePos.clone();
invMat.transformVec2s( [worldPos] );
byId('screenMouse').textContent = `screen: ${mousePos.x},${mousePos.y}`;
byId('worldMouse').textContent = `world: ${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)}`;
}
function onSliderInput(evt)
{
draw();
}
function updateSliderLabels()
{
byId('ofset1Output').textContent = byId('slider1').value;
byId('ofset2Output').textContent = byId('slider2').value;
}
function draw()
{
var can = byId('output');
var ctx = can.getContext('2d');
ctx.clearRect(0,0,can.width,can.height);
var orientMat = evaluateViewOrientationMatrix(0.06*can.width,can.height-24, 0,-1);
var scaleMat = computeWindowToViewPortMatrix(2052,1317, can.width,can.height);
var viewMat = scaleMat.clone();
viewMat.multiply(orientMat);
console.log('viewMat');
viewMat.print();
invMat = viewMat.clone().invert();
for (var i=0; i<9; i++)
invMat.elems[i] /= invMat.elems[8];
ctx.strokeStyle = '#fff';
var axisPts = [ new vec2d(0,1070), new vec2d(0,0), new vec2d(0.88*2052,0) ]; // xAxis line 88% of image width
var axis = viewMat.makeTransformedPoints(axisPts);
drawLine(axis[0].x,axis[0].y, axis[1].x,axis[1].y, ctx);
drawLine(axis[1].x,axis[1].y, axis[2].x,axis[2].y, ctx);
var lineEnds = [new vec2d(330,263), new vec2d(1455,809)];
var pts2 = viewMat.makeTransformedPoints(lineEnds);
drawCircle(pts2[0].x,pts2[0].y, 4, ctx);
drawCircle(pts2[1].x,pts2[1].y, 4, ctx);
drawLine(pts2[0].x,pts2[0].y, pts2[1].x,pts2[1].y, ctx);
var rawP3 = calcOffsetCoords(lineEnds[0].x,lineEnds[0].y, lineEnds[1].x,lineEnds[1].y, byId('slider1').value);
var rawP4 = calcOffsetCoords(lineEnds[1].x,lineEnds[1].y, lineEnds[0].x,lineEnds[0].y, byId('slider2').value);
var ofsPts = viewMat.makeTransformedPoints( [rawP3, rawP4] );
drawCircle(ofsPts[0].x,ofsPts[0].y, 4, ctx);
drawCircle(ofsPts[1].x,ofsPts[1].y, 4, ctx);
updateSliderLabels();
}
function calcOffsetCoords(x1,y1, x2,y2, offset)
{
var dx = x2 - x1;
var dy = y2 - y1;
var lineLen = Math.hypot(dx, dy);
var normDx=0, normDy=0;
if (lineLen != 0)
{
normDx = dx / lineLen;
normDy = dy / lineLen;
}
var resultX = x1 + (offset * normDx);
var resultY = y1 + (offset * normDy);
return {x:resultX,y:resultY};//new vec2d(resultX,resultY); //{x:resultX,y:resultY};
}
// Exercise 6-1:
// Write a procedure to implement the evaluateViewOrientationMatrix function that calculates the elements of the
// matrix for transforming world coordinates to viewing coordinates, given the viewing coordinate origin Porigin and
// the viewUp vector
function evalViewOrientMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
var worldUp = {x: worldUpVectorX, y: worldUpVectorY};
var len = Math.hypot(worldUp.x, worldUp.y);
if (len != 0)
len = 1.0 / len;
worldUp.x *= len;
worldUp.y *= len;
var worldRight = {x: worldUp.y, y: -worldUp.x};
var rotMat = svg.createSVGMatrix();
rotMat.a = worldRight.x;
rotMat.b = worldRight.y;
rotMat.c = worldUp.x;
rotMat.d = worldUp.y;
var transMat = svg.createSVGMatrix();
transMat = transMat.translate(screenOriginX, screenOriginY);
var result = rotMat.multiply(transMat);
return result;
}
function evaluateViewOrientationMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
var worldUp = new vec2d(worldUpVectorX, worldUpVectorY);
worldUp.normalize();
var worldRight = worldUp.clone().perp();
var rotMat = new mat3();
rotMat.elems[0] = worldRight.x; rotMat.elems[1] = worldRight.y;
rotMat.elems[3] = worldUp.x; rotMat.elems[4] = worldUp.y;
var transMat = new mat3();
transMat.translate(screenOriginX,screenOriginY);
var result = rotMat.clone();
result.multiply(transMat);
return result;
}
/*
0 1 2
3 4 5
6 7 8
translation
-----------
1 0 0
0 1 0
tX tY 1
scaling
---------
sX 0 0
0 sY 0
0 0 1
rotation
--------
cosX -sinX 0
sinX cosX 0
0 0 1
*/
// Exercise 6-2:
// Derive the window to viewport transformation equations 6-3 by first scaling the window to
// the size of the viewport and then translating the scaled window to the viewport position
function computeWindowToViewPortMatrix(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
var result = new mat3();
result.scaleEach(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
return result;
}
// returns an SVGMatrix
function compWnd2ViewMat(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
var result = svg.createSVGMatrix();
return result.scaleNonUniform(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
}
function drawLine(x1,y1,x2,y2,ctx)
{
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
function drawCircle(x,y,radius,ctx)
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, (Math.PI/180)*360, false);
ctx.stroke();
ctx.closePath();
}
canvas
{
background-color: black;
}
.container
{
display: inline-block;
background-color: #888;
border: solid 4px #555;
}
#screenMouse, #worldMouse, .control
{
display: inline-block;
width: calc(513px/2 - 2*8px);
margin-left: 8px;
}
<body>
<div class='container'>
<canvas id='output' width='513' height='329'></canvas><br>
<div id='screenMouse'></div><div id='worldMouse'></div>
<div>
<div class='control'>P2 ofs: <input id='slider1' type='range' min='0' max='500' value='301'><span id='ofset1Output'></span></div>
<div class='control'>P3 ofs: <input id='slider2' type='range' min='0' max='500' value='285'><span id='ofset2Output'></span></div>
</div>
</div>
</body>

Dygraphs - How do I restrict drawing to canvas

I have a graph where I am using underlays to draw vertical lines between the points. I have a line of code that restricts these vertical lines to NOT draw outside the active canvas. But when I use this underlayCallback, the 'points' are still drawn outside the canvas. If I remove my underlayCallback, the points are restricted to the canvas as one would expect. Here is what they look like and my code. (Sorry, the site is too secure to provide working sample.)
g[i] = new Dygraph(thisdiv, mylines, {
labels: graphlbls[i],
ylabel: graphunits[i].capitalizeFirstLetter(),
xlabel: '',
xLabelHeight:15,
yLabelWidth:15,
rightGap: 5,
labelsDivStyles: {
'text-align': 'right',
'background': 'none'
},
colors: ['#D48513','#1D6EB5'],
title: graphtitles[i],
titleHeight:23,
drawPoints: true,
showRoller: false,
drawXGrid: false,
drawYGrid: true,
strokeWidth: 0,
pointSize: 4,
highlightCircleSize: 6,
gridLineColor: "#ddd",
axisLabelFontSize: 12,
xAxisHeight: 20,
valueRange: [minval, maxval],
rangeSelectorHeight: 30,
showRangeSelector: true,
rangeSelectorPlotFillColor: '#ffffff',
rangeSelectorPlotStrokeColor: '#ffffff',
interactionModel: Dygraph.defaultInteractionModel,
axes: {
x: {
valueFormatter: function (ms) {
var d = new Date(ms);
var day = "0"+d.getDate();
var month = "0"+(d.getMonth()+1);
var year = d.getFullYear();
var hour = "0"+ d.getHours();
var min = "0"+d.getMinutes();
var p = "AM";
if (hour > 12) { p = "PM"; hour = hour - 12; }
if (df == 0) var dd = month.slice(-2)+"/"+day.slice(-2)+"/"+year;
if (df == 1) var dd = day.slice(-2)+"/"+month.slice(-2)+"/"+year;
if (tf == 0) var tt = hour.slice(-2)+":"+min.slice(-2)+" "+p+" ";
if (tf == 1) var tt = hour.slice(-2)+":"+min.slice(-2)+" ";
return dd + " - " + tt;
}
}
},
underlayCallback: function(ctx, area, g) {
//if (typeof(g[i]) == 'undefined') return; // won't be set on the initial draw.
var range = g.xAxisRange();
var rows = g.numRows();
// get max and min y
for (var i = 0; i < rows; i++) {
miny = 99999;
maxy = -99999;
xx = g.getValue(i,0);
if (xx < range[0] || xx > range[1]) continue; // constrain to graph canvas
for (var j=1; j<= range.length; j++) {
if (g.getValue(i,j) <= miny) miny = g.getValue(i,j);
if (g.getValue(i,j) >= maxy) maxy = g.getValue(i,j);
}
p1 = g.toDomCoords(xx, miny);
p2 = g.toDomCoords(xx, maxy);
ctx.strokeStyle = "rgba(192,192,224,1)";
ctx.lineWidth = 1.0;
ctx.beginPath();
ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
});
You're calling ctx.restore() many times without corresponding calls to ctx.save(). This pops off dygraphs' own drawing context, including the clipping rectangle. Make one call to save at the top of your underlayCallback and one to restore at the end.
Stepping back a bit, what you're doing might be easier with a custom plotter, rather than an underlayCallback.

Chart.js click on labels, using bar chart

i need help with my Chart.js interactivity. When I click on the label, I need to return the column(index) number at which I clicked.
I tried to use getElementsAtEvent but it only work if I click directly at chart.
This http://jsfiddle.net/yxz2sjam/ is pretty much what I am looking for but getPointsAtEvent is no longer available in the new versions.
canvas.onclick = function (evt) {
var points = chart.getPointsAtEvent(evt);
alert(chart.datasets[0].points.indexOf(points[0]));
};
I also found this http://jsfiddle.net/1Lngmtz7/ but it isn't working with bar chart.
var ctx = document.getElementById("myChart").getContext("2d");
var myRadarChart = new Chart(ctx, {
type: 'radar',
data: data
})
$('#myChart').click(function (e) {
var helpers = Chart.helpers;
var eventPosition = helpers.getRelativePosition(e, myRadarChart.chart);
var mouseX = eventPosition.x;
var mouseY = eventPosition.y;
var activePoints = [];
helpers.each(myRadarChart.scale.ticks, function (label, index) {
for (var i = this.getValueCount() - 1; i >= 0; i--) {
var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5);
var pointLabelFontSize = helpers.getValueOrDefault(this.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize);
var pointLabeFontStyle = helpers.getValueOrDefault(this.options.pointLabels.fontStyle, Chart.defaults.global.defaultFontStyle);
var pointLabeFontFamily = helpers.getValueOrDefault(this.options.pointLabels.fontFamily, Chart.defaults.global.defaultFontFamily);
var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
ctx.font = pointLabeFont;
var labelsCount = this.pointLabels.length,
halfLabelsCount = this.pointLabels.length / 2,
quarterLabelsCount = halfLabelsCount / 2,
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
var width = ctx.measureText(this.pointLabels[i]).width;
var height = pointLabelFontSize;
var x, y;
if (i === 0 || i === halfLabelsCount)
x = pointLabelPosition.x - width / 2;
else if (i < halfLabelsCount)
x = pointLabelPosition.x;
else
x = pointLabelPosition.x - width;
if (exactQuarter)
y = pointLabelPosition.y - height / 2;
else if (upperHalf)
y = pointLabelPosition.y - height;
else
y = pointLabelPosition.y
if ((mouseY >= y && mouseY <= y + height) && (mouseX >= x && mouseX <= x + width))
activePoints.push({ index: i, label: this.pointLabels[i] });
}
}, myRadarChart.scale);
var firstPoint = activePoints[0];
if (firstPoint !== undefined) {
alert(firstPoint.index + ': ' + firstPoint.label);
}
});
Thank for response.
I solve the problem with
document.getElementById("chart").onclick = function(e)
{
var activeElement = weatherMainChart.lastTooltipActive;
console.log(activeElement[0]._index);
};
this solution register clicks on chart and label, then I restricted it with e.layerY to register only clicks on label section.
document.getElementById("chart").onclick = function(e)
{
var activeElement = weatherMainChart.lastTooltipActive;
if(e.layerY > 843 && e.layerY < 866 && activeElement[0] !== undefined)
console.log(activeElement[0]._index);
};
If you add a click handler through the onClick option you can use the following code using the getElementsAtEventForMode() call:
function handleClick(evt) {
var col;
switch(chartType) {
case "horizontalBar":
this.getElementsAtEventForMode(evt, "y", 1).forEach(function(item) { col = item._index });
break;
case "bar":
this.getElementsAtEventForMode(evt, "x", 1).forEach(function(item) { col = item._index });
break;
}
if (!col) {
return;
}
alert("Column " + col + " was selected");
};
You'll probably need to add extra switch checks for other chart types but I'm sure you get the idea.
Using version 2.4.0, i created an onClick Event, and inside it
var activeIndex = localChart.tooltip._lastActive[0]._index;
var clickCoordinates = Chart.helpers.getRelativePosition(e, localChart.chart);
if (clickCoordinates.y >= 530) { //custom value, depends on chart style,size, etc
alert("clicked on " + localChart.data.labels[activeIndex]);
}
I Solved this problem with single or multiple label click you will be find using true/false
First you need to set your chartJs Id click
below code SessionChart = Your ChartJs ID e.g. ("myChart") I was replace it for my Id
document.getElementById("SessionChart").onclick = function (evt) {
var meta = SubscriberSessionChart.getDatasetMeta(0);
if (meta.$filler.chart.legend.legendItems[0].text.toLowerCase() "sessions")
{
if (meta.$filler.chart.legend.legendItems[0].hidden) {
sessionHidden = true;
}
}
}
here "sessions" = first label text
meta.$filler.chart.legend.legendItems[0].text.toLowerCase() = is your first label
from Array so you can get multiple label's click here true / false
if (meta.$filler.chart.legend.legendItems[0].hidden) = your label is not active then
you will get hidden true otherwise you will get false if not tick on label
by default label tick hidden is false in chart js

Can't get tumblr feed to show up

Here is the Script i'm using to stream my tumblr blog posts to my website, i have the correct API Key, and Base host name, still nothing appears. Is there some other information I'm bypassing ? any help will be greatly appreciated.
<script type="text/javascript">
var $_api_key = 'PhLtoFx1aCBtu9IG3pIuT9BPd0Vxb602nPZtdMKxHvHM89VH28',
$_base_hostname = 'http://amongtheprimeblog.tumblr.com',
$_full_width = 1170;
$_rows = 2
$_cols = 3;
$_min_width = $_full_width / $_cols,
$_nsfw_tag = 'nsfw';
$_display_limit = $_rows * $_cols,
$_post_limit = $_display_limit * 2,
$_photos_api = 'https://api.tumblr.com/v2/blog/'+$_base_hostname+'/posts/photo?limit='+$_post_limit+'&api_key='+$_api_key,
jQuery.ajax($_photos_api,
{
dataType: 'jsonp',
success: function ($_posts) {
//console.log($_posts);
var $_photos = [],
$_displayed = 0 ;
for(var i=0; i < $_post_limit; i++) {
var $_post_url = $_posts.response.posts[i].post_url.replace('http://','https://'),
$_tag_count = $_posts.response.posts[i].tags.length,
$_photo_count = $_posts.response.posts[i].photos[0].alt_sizes.length,
$_post_photo_big = $_posts.response.posts[i].photos[0].original_size.url.replace('http://','https://'),
$_post_photo_big_width = $_posts.response.posts[i].photos[0].original_size.width,
$_post_photo_big_height = $_posts.response.posts[i].photos[0].original_size.height,
$_can_display = true;
// Check NSFW to keep Adroll happy!
for (var k=0; k < $_tag_count; k++ ){
if($_posts.response.posts[i].tags[k] == $_nsfw_tag) {
$_can_display = false;
}
}
// Iterate for most suitable image size.
if($_can_display) {
for (var j=0; j < $_photo_count; j++ ) {
if($_posts.response.posts[i].photos[0].alt_sizes[j].width > $_min_width) {
$_post_photo = $_posts.response.posts[i].photos[0].alt_sizes[j].url.replace('http://','https://');
} else {
break;
}
}
$_photos.push('<a id="tumblr_'+i+'" class="tumblr_post" href="'+$_post_url+'" target="_blank" data-src-big="'+$_post_photo_big+'" data-src-big-width="'+$_post_photo_big_width+'" data-src-big-height="'+$_post_photo_big_height+'"><img src="'+$_post_photo+'"/></a>');
$_displayed++;
if( $_displayed == $_display_limit) {
break;
}
}
}
jQuery('#tumblr_images').html($_photos.join(''));
},
}
);
function loadTumblrModal(id) {
var $_post = jQuery('#'+id),
$_post_next = $_post.next('.tumblr_post').attr('id'),
$_post_prev = $_post.prev('.tumblr_post').attr('id'),
$_post_link = $_post.attr('href'),
$_modal = jQuery('#tumblrModal'),
$_post_photo_src = $_post.attr('data-src-big').replace('http://','https://'),
$_post_photo_height = parseInt($_post.attr('data-src-big-height')),
$_post_photo_width = parseInt($_post.attr('data-src-big-width')),
$_post_photo_ratio = $_post_photo_width / $_post_photo_height,
$_window_height = jQuery(window).height(),
$_dialog_padding = 20,
$_dialog_margin = 20,
$_dialog_height = jQuery(window).height() - ($_dialog_margin * 2),
$_follow_iframe = '<iframe class="follow-button " src="https://platform.tumblr.com/v1/follow_button.html?button_type=1&tumblelog=theiloveuglyblog&color_scheme=dark" frameborder="0" scrolling="no" width="118" height="25"></iframe>';
// Re-Calibrate Dialog if Image is smaller than Window
if($_post_photo_height < $_dialog_height) {
$_dialog_height = $_post_photo_height;
}
// Set Dialog & Image Dimensions
var $_image_height = $_dialog_height - ($_dialog_padding *2),
$_image_width = $_image_height * $_post_photo_ratio,
$_dialog_width = $_image_width + ($_dialog_padding * 2);
// Force First/Last if Undefined for Next/Prev. Creates looping
if(typeof $_post_next === 'undefined') {
$_post_next = $_post.parent().find('.tumblr_post').first().attr('id');
}
if(typeof $_post_prev === 'undefined') {
$_post_prev = $_post.parent().find('.tumblr_post').last().attr('id');
}
// FireUp all the Modal
$_modal.find('.modal-dialog').css({
"width" : $_dialog_width + 'px',
"height" : $_dialog_height + 'px',
"margin-left" : -($_dialog_width/2) + 'px',
"margin-top" : -($_dialog_height/2) + 'px',
});
$_modal.find('.tumblr_img').html('<img src="'+$_post_photo_src+'" width="'+$_image_width+'" height="'+$_image_height+'"/>');
$_modal.find('.tumblr_iframe').html($_follow_iframe);
$_modal.find('#tumblr-next').attr('data-id',$_post_next);
$_modal.find('#tumblr-prev').attr('data-id',$_post_prev);
}
jQuery('#tumblr_images').on('click','a',function(e){
var $_modal = jQuery('#tumblrModal');
e.preventDefault();
loadTumblrModal(jQuery(this).attr('id'));
$_modal.modal('show');
});
jQuery(document).on('click','.tumblrLink',function(e){
loadTumblrModal(jQuery(this).attr('data-id'));
});
</script>

kineticjs - createImageRegion for pixels with opacity > 0 not only 1

So the createImageRegion method ignores all pixels with a tiny bit of alpha/opacity.
How can you make this function so that also pixels with an opacity of .5 or something will be count for hitdetection?
I looked into KineticJS. A colorKey is added to a hitregion, but it transforms the key to a hex key with no alpha. I can't figure out a way how i could make this work.
Help is much appriciated!
this is where the magic happens. But i don't understand how i can include pixels whith any kind of transpanacy but 0
createImageHitRegion: function (callback) {
var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height);
var context = canvas.getContext();
context.drawImage(this.attrs.image, 0, 0);
try {
var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight());
var data = imageData.data;
var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey);
// replace non transparent pixels with color key
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] = rgbColorKey.r;
data[i + 1] = rgbColorKey.g;
data[i + 2] = rgbColorKey.b;
// i+3 is alpha (the fourth element)
}
var that = this;
Kinetic.Type._getImage(imageData, function (imageObj) {
that.imageHitRegion = imageObj;
if (callback) {
callback();
}
});
}
catch (e) {
Kinetic.Global.warn('Unable to create image hit region. ' + e.message);
}
}
When i include: data[i + 3] = 255 (rgbColorKey.a) does not excist, none of the imageevents are working anymore
I found the answer:
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] = rgbColorKey.r;
data[i + 1] = rgbColorKey.g;
data[i + 2] = rgbColorKey.b;
if (!ignoreAlpha && data[i + 3] > 0) {
data[i + 3] = 255;
}
}
This way, every pixel which has a bit of transparancy left, it will get a full color. So if you prototype this to kinetixJS it should work:
Kinetic.Image.prototype.createImageHitRegion = function (callback, ignoreTransparantPixels) {
var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height);
var context = canvas.getContext();
var _ignoreTransparantPixels;
if(typeof ignoreTransparantPixels == 'undefined'){
_ignoreTransparantPixels = false;
}
context.drawImage(this.attrs.image, 0, 0);
try {
var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight());
var data = imageData.data;
var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey);
// replace non transparent pixels with color key
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] = rgbColorKey.r;
data[i + 1] = rgbColorKey.g;
data[i + 2] = rgbColorKey.b;
if (!_ignoreTransparantPixels && data[i + 3] > 0) {
data[i + 3] = 255;
}
//trace(data[i + 3]);
// i+3 is alpha (the fourth element)
}
var that = this;
Kinetic.Type._getImage(imageData, function (imageObj) {
that.imageHitRegion = imageObj;
if (callback) {
callback();
}
});
}
catch (e) {
Kinetic.Global.warn('Unable to create image hit region. ' + e.message);
}
}