TensorFlow multi class training and prediction - tensorflow

The following code (working), train a model to recognize cats and make a prediction on the selected picture. (Code TensorFlowJS but the question is generally TensorFlow)
So far it is only predicting one class ("cat"), so that a car or a dog would be for example 80% a cat.
Question:
How do i add other classes (like "dog") ?
Should it look like that (abstracted): model.fit([img1, img2, img3], [label1, label2, label3] ...) ?
I don't get it:
What is the relation between the labels and the training set.
Here is the code (please ignore the "Predict" part for now):
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#1.2.7"> </script>
<script src="https://unpkg.com/#tensorflow-models/mobilenet"></script>
</head>
<body>
<div class="container mt-5">
<div class="row">
<input id ="image-selector" class="form-control border-0" type="file"/>
</div>
<div class="row">
<div class="col">
<h2>Prediction</h2>
<ol id="prediction-list"></ol>
</div>
</div>
<div class="row">
<div class="col-12">
<h2 class="ml-3">Image</h2>
<canvas id="canvas" width="400" height="300" style="border:1px solid #000000;"></canvas>
</div>
</div>
</div>
<div id="training-images">
<img width="400" height="300" class="train-image cat" src="training-images/cat.jpg" />
<img width="400" height="300" class="train-image cat" src="training-images/cat2.jpeg" />
<img width="400" height="300" class="train-image cat" src="training-images/cat3.jpeg" />
<img width="400" height="300" class="train-image cat" src="training-images/cat4.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog2.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog3.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog4.jpeg" />
</div>
</body>
<script>
const modelType = "mobilenet";
const model = tf.sequential();
const label = ['cat'];
var ys, setLabel, input, canvas, context;
input = document.getElementById("image-selector");
canvas = document.getElementById("canvas");
context = canvas.getContext('2d');
//-------------------------- Training: --------------------------------
window.addEventListener('load', (event) => {
// Labels
setLabel = Array.from(new Set(label));
ys = tf.oneHot(tf.tensor1d(label.map((a) => setLabel.findIndex(e => e === a)), 'int32'), 10);
console.log('ys:::'+ys);
// Prepare model :
model.add(tf.layers.conv2d({
inputShape: [224, 224 , 3],
kernelSize: 5,
filters: 8,
strides: 2,
activation: 'relu',
kernelInitializer: 'VarianceScaling'
}));
model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));
model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));
model.add(tf.layers.flatten({}));
model.add(tf.layers.dense({units: 64, activation: 'relu'}));
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
model.compile({
loss: 'meanSquaredError',
optimizer : 'sgd'
});
// Prepare training images
var images = [];
for(var i = 0; i < 40; i++) {
let img = preprocessImage(document.getElementsByClassName("cat")[i], modelType);
images.push(tf.reshape(img, [1, 224, 224, 3],'resize'));
}
console.log("processed images : ");
console.log(images);
trainModel(images);
});
async function trainModel(images) {
for(var i = 0; i < images.length; i++) {
await model.fit(images[i], ys, {epochs: 100, batchSize: 32}).then((loss) => {
const t = model.predict(images[i]);
console.log('Prediction:::'+t);
pred = t.argMax(1).dataSync(); // get the class of highest probability
const labelsPred = Array.from(pred).map(e => setLabel[e]);
console.log('labelsPred:::'+labelsPred);
}).catch((e) => {
console.log(e.message);
})
}
console.log("Training done!");
}
//-------------------------- Predict: --------------------------------
input.addEventListener("change", function() {
var reader = new FileReader();
reader.addEventListener("loadend", function(arg) {
var src_image = new Image();
src_image.onload = function() {
canvas.height = src_image.height;
canvas.width = src_image.width;
context.drawImage(src_image, 0, 0);
var imageData = canvas.toDataURL("image/png");
runPrediction(src_image)
}
src_image.src = this.result;
});
var res = reader.readAsDataURL(this.files[0]);
});
async function runPrediction(imageData){
let tensor = preprocessImage(imageData, "mobilenet");
const resize_image = tf.reshape(tensor, [1, 224, 224, 3],'resize');
let prediction = await model.predict(tensor).data();
console.log('prediction:::'+ prediction);
let top5 = Array.from(prediction)
.map(function(p,i){
return {
probability: p,
className: prediction[i]
};
}).sort(function(a,b){
return b.probability-a.probability;
}).slice(0,1);
$("#prediction-list").empty();
top5.forEach(function(p){
$("#prediction-list").append(`<li>${p.className}:${p.probability.toFixed(6)}</li>`);
});
}
//-------------------------- Helpers: --------------------------------
function preprocessImage(image, modelName)
{
let tensor = tf.browser.fromPixels(image)
.resizeNearestNeighbor([224,224])
.toFloat();
let offset=tf.scalar(127.5);
return tensor.sub(offset)
.div(offset)
.expandDims();
}
</script>
The code is based on the TFJS documentation and a comment on the github : https://github.com/tensorflow/tfjs/issues/1288
UPDATE :
So I need X and Y to be the same length for X:images and Y:labels, with Y1 being the label for X1 and so on...
I tried:
ys:::Tensor (with only 2 classes represented in the training data set) :
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]]
One image + all labels -> with "model.fit(images[i], ys, {epochs: 100})...", I get:
Error: "Input Tensors should have the same number of samples as target Tensors. Found 1 input sample(s) and 10 target sample(s)."
One image + one label -> with "model.fit(images[i], ys[i], {epochs: 100})...", I get:
Error: "Cannot read property 'shape' of null", i guess ys is a tensor but y[i] is not.
All images + all labels -> with "model.fit(images, ys, {epochs: 100})...", I get:
Error: "when checking model input: the Array of Tensors that you are passing to your model is not the size the model expected.
Expected to see 1 Tensor(s), but instead got the following list of Tensor(s): Tensor ..."
Guess: I need to put all images in one tensor with the same structure as ys.
SOLVED :
After solving the problem with the labels thanks to Rishabh Sahrawat, I had to merge all tensor(images) in to one with the help of tf.concat(...).
[tensorImg1, tensorImg2, tensorImg3, tensorImg4, ...] x tensor[label1, label2, label3, label4, ...]
->
tensor[dataImg1, dataImg2, dataImg3, dataImg4, ...] x tensor[label1, label2, label3, label4, ...]
Updated code :
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#1.2.7"> </script>
<script src="https://unpkg.com/#tensorflow-models/mobilenet"></script>
</head>
<body>
<div class="container mt-5">
<div class="row">
<input id ="image-selector" class="form-control border-0" type="file"/>
</div>
<div class="row">
<div class="col">
<h2>Prediction</h2>
<ol id="prediction-list"></ol>
</div>
</div>
<div class="row">
<div class="col-12">
<h2 class="ml-3">Image</h2>
<canvas id="canvas" width="400" height="300" style="border:1px solid #000000;"></canvas>
</div>
</div>
</div>
<div id="training-images">
<img width="400" height="300" class="train-image cat" src="training-images/cat.jpg" />
<img width="400" height="300" class="train-image cat" src="training-images/cat2.jpeg" />
<img width="400" height="300" class="train-image cat" src="training-images/cat3.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog2.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog3.jpeg" />
<img width="400" height="300" class="train-image dog" src="training-images/dog4.jpeg" />
</div>
</body>
<script>
const modelType = "mobilenet";
const model = tf.sequential();
var labels = ['cat', 'dog'];
var ys, setLabel, input, canvas, context;
input = document.getElementById("image-selector");
canvas = document.getElementById("canvas");
context = canvas.getContext('2d');
//-------------------------- Training: --------------------------------
window.addEventListener('load', (event) => {
// Prepare model :
prepareModel();
// Prepare training images
var images = [];
var trainLabels = []
for(var i = 0; i < document.getElementsByClassName('train-image').length; i++) {
let img = preprocessImage(document.getElementsByClassName('train-image')[i], modelType);
//images.push(tf.reshape(img, [1, 224, 224, 3],'resize'));
images.push(img);
if (document.getElementsByClassName('train-image')[i].classList.contains("cat")){
trainLabels.push(0)
} else {
trainLabels.push(1)
}
}
console.log(labels)
setLabel = Array.from(labels);
ys = tf.oneHot(trainLabels, 2);
console.log('ys:::'+ys);
console.log(images);
trainModel(images);
});
async function trainModel(images) {
for(var i = 0; i < images.length; i++) {
await model.fit(tf.concat(images, 0), ys, {epochs: 100}).then((loss) => {
const t = model.predict(images[i]);
console.log('Prediction:::'+t);
pred = t.argMax().dataSync(); // get the class of highest probability
//const labelsPred = Array.from(pred).map(e => setLabel[e]);
//console.log('labelsPred:::'+labelsPred);
}).catch((e) => {
console.log(e.message);
})
}
console.log("Training done!");
}
//-------------------------- Predict: --------------------------------
input.addEventListener("change", function() {
var reader = new FileReader();
reader.addEventListener("loadend", function(arg) {
var src_image = new Image();
src_image.onload = function() {
canvas.height = src_image.height;
canvas.width = src_image.width;
context.drawImage(src_image, 0, 0);
var imageData = canvas.toDataURL("image/png");
runPrediction(src_image)
}
src_image.src = this.result;
});
var res = reader.readAsDataURL(this.files[0]);
});
async function runPrediction(imageData){
let tensor = preprocessImage(imageData, "mobilenet");
const resize_image = tf.reshape(tensor, [1, 224, 224, 3],'resize');
let prediction = await model.predict(tensor).data();
console.log('prediction:::'+ prediction);
let top5 = Array.from(prediction)
.map(function(p,i){
return {
probability: p,
className: prediction[i]
};
}).sort(function(a,b){
return b.probability-a.probability;
}).slice(0,1);
$("#prediction-list").empty();
top5.forEach(function(p){
$("#prediction-list").append(`<li>${p.className}:${p.probability.toFixed(6)}</li>`);
});
}
//-------------------------- Helpers: --------------------------------
function prepareModel(){
model.add(tf.layers.conv2d({
inputShape: [224, 224 , 3],
kernelSize: 5,
filters: 8,
strides: 2,
activation: 'relu',
kernelInitializer: 'VarianceScaling'
}));
model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));
model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));
model.add(tf.layers.flatten({}));
model.add(tf.layers.dense({units: 64, activation: 'relu'}));
model.add(tf.layers.dense({units: 2, activation: 'softmax'}));
model.compile({
loss: 'meanSquaredError',
optimizer : 'sgd'
});
model.summary()
}
function preprocessImage(image, modelName)
{
let tensor = tf.browser.fromPixels(image)
.resizeNearestNeighbor([224,224])
.toFloat();
let offset=tf.scalar(127.5);
return tensor.sub(offset)
.div(offset)
.expandDims();
}
</script>

How do i add other classes (like "dog") ?
You can make model predict also on another class is by adding the new class to your training dataset. Let's say you added Dog class, so now your dataset consists Cat and Dog pictures.
Should it look like that (abstracted): model.fit([img1, img2, img3], [label1, label2, label3] ...)
Yes, images x = [img1, img2, img3] and labels to corresponding images, y = [label1, label2, label3]. In x, img1 or img2 or any other image can be a cat image or dog image. For simplicity, you can feed images represented as numpy arrays. Here is how the input training data must look like.
What is the relation between the labels and the training set.
Labels are a part of training set. If you are performing supervised classification then labels have to be fed along with your input features (images).
UPDATE for updated question
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]]
In this you have a shape mismatch. The shape here is (10,10) but the model expects label input with shape (10,).
If you have two classes, you don't need to represent one class with [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]] or other with [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] in Y (Label). What does the rest of the zeroes do?. Just keep it simple and define as follows.
If you have a cat you label it 0, and for dog image, you label it 1 or vice-versa.
and then you feed it like [0,1,0], here first 0 is the label for img1, 1 for img2 and 0 for img3.

Related

how to initialize canvas and react it with vue

I'm new to Vue.js and I learned some basic skills. Right now I'm trying to solve real problems with it.
I'm trying to draw some donuts with Vue.js. I can make it without Vue easily, but it confuse me while I'm trying to use Vue.js.
/*
canvas: HTMLCanvas node
ratio: number range: [0, 1]
*/
function pie (canvas, ratio) {
function arc (ctx, color, radius, radian) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, radius, 0, radian);
ctx.closePath();
ctx.fill();
}
var width = canvas.clientWidth,
height = canvas.clientHeight,
outRadius = Math.ceil(width / 2 - 10),
innerRadius = Math.ceil(outRadius * 0.8);
var ctx = canvas.getContext("2d");
ctx.save();
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.translate(width / 2, height / 2);
ctx.rotate(-Math.PI / 2);
arc(ctx, "steelblue", outRadius, Math.PI * 2);
arc(ctx, "yellow", outRadius + 5, Math.PI * 2 * ratio);
arc(ctx, "white", innerRadius, Math.PI * 2);
ctx.restore();
}
Vue.component("pie", {
props: ["pies"],
methods: {
draw: pie
},
template: `
<ul class="pie">
<li v-for="pie in pies">
<div class="pie__content">
<h3 class="pie__header">{{pie.ratio}}</h3>
<canvas v-on:click="draw($event.target, pie.ratio)" width="200" height="200"></canvas>
</div>
<h3 class="pie__name">{{pie.name}}</h3>
</li>
</ul>
`
});
var vm = new Vue({
el: "#app",
data: {
pies: [
{ratio: 0.45, name: "CPU"},
{ratio: 0.75, name: "Memory"},
{ratio: 0.15, name: "Drive"},
]
}
});
canvas { border: 1px solid gray; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<pie v-bind:pies="pies"></pie>
</div>
firstly, I don't know how to initialize those canvas. Run those code above you will find those canvas are blank unless user click on them. Absolutely this is not whta I want, but it seems event is the only way make it work;
secondly, If I change ratio, such as: vm.pies[0].ratio = 0.78, the
related canvas give no response.
Any comments will be appreciated!
What I miss is the Vue Instance Lifecycle. Vue instance offer a mounted property which will run after the component is rendered.
Firstly, I give the canvas element a ref attribute which make it easy refered on late manipulation.
Creating an mounted property function which will run after component is rendered. This could be the initialize function.
Creating an computed getter function formatRatio which will redraw the canvas if availble and finally return the formated ratio string in percent format. This part will make the the pie charts reactive with bind data.
Here is the code:
function pie (canvas, ratio) {
function arc (ctx, color, radius, radian) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, radius, 0, radian);
ctx.closePath();
ctx.fill();
}
var width = canvas.clientWidth,
height = canvas.clientHeight,
outRadius = Math.ceil(width / 2 - 10),
innerRadius = Math.ceil(outRadius * 0.8);
var ctx = canvas.getContext("2d");
ctx.save();
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.translate(width / 2, height / 2);
ctx.rotate(-Math.PI / 2);
arc(ctx, "steelblue", outRadius, Math.PI * 2);
arc(ctx, "yellow", outRadius + 5, Math.PI * 2 * ratio);
arc(ctx, "white", innerRadius, Math.PI * 2);
ctx.restore();
}
Vue.component("pie-canvas", {
props: ["pie"],
computed: {
formatRatio: function () {
// when component is rendered canvas element will be avaible
if (this.$refs.canvas) {
pie(this.$refs.canvas, this.pie.ratio);
}
return Math.round(this.pie.ratio * 1000) / 10 + "%";
}
},
mounted: function () {
pie(this.$refs.canvas, this.pie.ratio);
},
template: `
<div class="pie__content">
<h3 class="pie__header">{{ formatRatio }}</h3>
<canvas ref="canvas" width="200" height="200"></canvas>
</div>
`
});
var vm = new Vue({
el: "#app",
data: {
pies: [
{ratio: 0.45, name: "CPU"},
{ratio: 0.75, name: "Memory"},
{ratio: 0.15, name: "Drive"},
]
}
});
canvas { border: 1px solid gray; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="pie">
<li v-for="pie in pies">
<pie-canvas v-bind:pie="pie"></pie-canvas>
<h3 class="pie__name">{{pie.name}}</h3>
</li>
</ul>
</div>

Detect if a user has created a 4x4 line in a 4x4 grid of divs

So I am building my own custom bingo table that is 4x4 with vue. I have everything down except how to detect wether a user has formed a 4x4 line that is horizontal, diagonal, or vertical
Inside my data function I have an array that represents the 4x4
[9, 13, 28, 24],
[11, 22, 15, 43],
[54, 5, 37, 4],
[27, 40, 12, 36]
My question is how can I check to see if a user has clicked a 4x4 line? No code is needed as an answer I just want to know how I can approach this problem.
Given an n x n matrix
Horizontal:
Are there n selected elements that have the same rowIndex?
Vertical:
Are there n selected elements that have the same columnIndex?
Diagonal (Top Left to Bottom Right)
Are there n elements that have the same rowIndex as their columnIndex?
Diagonal (Top Right to Bottom Left)
Are there n elements that satisfy (length(row) - 1) - rowIndex == columnIndex?
const Card = Vue.component('card', {
template: '#card',
props: {
playerCard: Array
},
data() {
return {
selectedVals: [],
rowCounts: {},
colCounts: {}
}
},
computed: {
horizontalNumberToWin() {
return this.playerCard[0].length;
},
verticalNumberToWin() {
return this.playerCard.length;
},
diagonalNumberToWin() {
return this.playerCard.length;
},
isDiagonal() {
if (this.selectedVals.length < this.diagonalNumberToWin) return false;
// top left to bottom right
// [0, 0] [1, 1], [2, 2], [3, 3], etc..
const isTLtoBR = this.selectedVals.filter(val => val[0] === val[1]);
if (isTLtoBR.length >= this.diagonalNumberToWin) return true;
// top right to bottom left
// [0, 3], [1, 2], [2, 1], [3, 0], etc..
const rowLen = this.playerCard[0].length;
const isTRtoBL = this.selectedVals.filter(val => {
return (rowLen -1) - val[0] === val[1];
});
if (isTRtoBL.length >= this.diagonalNumberToWin) return true;
return false;
},
isHorizontal() {
if (this.selectedVals.length < this.horizontalNumberToWin) return false;
return Object.values(this.rowCounts).some(row => row >= this.horizontalNumberToWin);
},
isVertical() {
if (this.selectedVals.length < this.verticalNumberToWin) return false;
return Object.values(this.colCounts).some(col => col >= this.verticalNumberToWin);
},
},
methods: {
onCardClicked(coord) {
this.selectedVals.push(coord);
this.updateCounts(coord);
},
cardDisabled(coord) {
return this.selectedVals.some(vals => vals[0] === coord[0] && vals[1] === coord[1]);
},
updateCounts(coord) {
const rowIndex = coord[0];
const colIndex = coord[1];
this.rowCounts[rowIndex] = this.rowCounts[rowIndex] ? this.rowCounts[rowIndex] + 1 : 1;
this.colCounts[colIndex] = this.colCounts[colIndex] ? this.colCounts[colIndex] + 1 : 1;
}
}
});
new Vue({
el: '#app',
components: {
Card,
},
data: {
playerCard: [
[9, 13, 28, 24],
[11, 22, 15, 43],
[54, 5, 37, 4],
[27, 40, 12, 36]
],
},
})
#app {
display: flex;
flex-direction: row;
justify-content: center;
}
.board {
max-width: 500px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<card :player-card="playerCard" />
</div>
<template id="card">
<div>
<p>Horizontal: {{ isHorizontal }}</p>
<p>Vertical: {{ isVertical }}</p>
<p>Diagonal: {{ isDiagonal }}</p>
<div class="board">
<template v-for="(row, rowIndex) in playerCard">
<button
v-for="(col, colIndex) in row"
:key="col"
:disabled="cardDisabled([rowIndex, colIndex])"
#click="onCardClicked([rowIndex, colIndex])">
{{ col }}
</button>
</template>
</div>
</div>
</template>
First, I think you should add isClick to check whether the user has clicked the card or not.
[
[
{ Number: 9 , isClick: false },
{ Number: 13 , isClick: false },
{ Number: 28 , isClick: false },
{ Number: 24 , isClick: false }
],
// other array ...
]
Second, bind your data into html (Maybe you should used twice v-for to achieve it). When user click the card, set isClick = true.
Third, write your own logic to check if a user has clicked a 4x4 line.

Can a Chartist line graph be animated left to right?

I see animations for Chartist line graphs (http://gionkunz.github.io/chartist-js/examples.html#example-line-path-animation), but none where the line literally draws itself out from left to right. Is that possible?
Not my solution, but it seems to do what you want.
HTML
<div class="ct-chart ct-golden-section"></div>
CSS
$color1: #ada8b6; //rgba(173, 168, 182, 100)
$color2: #ffeedb; //rgba(255, 238, 219, 100)
$color3: #4c3b4d; //rgba(76, 59, 77, 100)
$color4: #a53860; //rgba(165, 56, 96, 100)
$color5: #61c9a8; //rgba(97, 201, 168, 100)
body {
width: 100vw;
height: 100vh;
background: #111;
}
.ct-chart {
width: 90vw;
max-width: 1100px;
height: 375px;
margin: 5vh 6.5vw;
svg {
width: 100%;
}
}
.ct-grids line {
stroke: $color3;
opacity: 0.4;
}
.ct-labels span {
color: $color3;
}
#mixin pathseries($length, $delay, $strokecolor) {
stroke-dasharray: $length;
stroke-dashoffset: $length;
animation: draw 1s $delay ease both;
fill: none;
stroke: $strokecolor;
opacity: 0.8;
}
.ct-series-a {
#include pathseries(1093, 0s, $color1);
}
.ct-series-b {
#include pathseries(1665, 0.25s, $color5);
}
.ct-series-c {
#include pathseries(1644, 0.5s, $color2);
}
.ct-series-d {
#include pathseries(1540, 0.75s, $color4);
}
#keyframes draw {
to {
stroke-dashoffset: 0;
}
}
JS
new Chartist.Line('.ct-chart', {
labels: [1, 2, 3, 4, 5, 6, 7, 8],
series: [
[11, 12, 13, 11, 12, 10, 11, 10],
[12, 11, 17, -1, 0, 18, -2, 8],
[0, 8, 12, 1, 15, 3, 18, 1],
[3, 2, 12, 15, 16, 3, 18, -3]
]
}, {
high: 20,
low: -3,
fullWidth: true,
// As this is axis specific we need to tell Chartist to use whole numbers only on the concerned axis
axisY: {
onlyInteger: true,
offset: 20
}
});
setTimeout (
function() {
var path = document.querySelector('.ct-series-a path');
var length = path.getTotalLength();
console.log(length);
},
3000);

How to export google chart in pdf?

I have draw google chart. Now, I want to put button to save the chart in pdf format. I do look from here Save google charts as pdf and other questions available in stack but it doesn't work.
Print png image by google chart already used but it just open a new tab with the png image but it doesnt open the save as pdf window for user.
Do anyone knows any ways to do it?
you can use jsPDF to create a PDF
use method addImage to add the chart's image uri to the pdf
see following working snippet...
google.charts.load('current', {
packages: ['controls', 'corechart', 'table']
}).then(function () {
var data = new google.visualization.DataTable();
data.addColumn('number', 'X');
data.addColumn('number', 'y0');
data.addRows([
[0, 0], [1, 10], [2, 23], [3, 17], [4, 18], [5, 9],
[6, 11], [7, 27], [8, 33], [9, 40], [10, 32], [11, 35],
[12, 30], [13, 40], [14, 42], [15, 47], [16, 44], [17, 48],
[18, 52], [19, 54], [20, 42], [21, 55], [22, 56], [23, 57],
[24, 60], [25, 50], [26, 52], [27, 51], [28, 49], [29, 53],
[30, 55], [31, 60], [32, 61], [33, 59], [34, 62], [35, 65],
[36, 62], [37, 58], [38, 55], [39, 61], [40, 64], [41, 65],
[42, 63], [43, 66], [44, 67], [45, 69], [46, 69], [47, 70],
[48, 72], [49, 68], [50, 66], [51, 65], [52, 67], [53, 70],
[54, 71], [55, 72], [56, 73], [57, 75], [58, 70], [59, 68],
[60, 64], [61, 60], [62, 65], [63, 67], [64, 68], [65, 69],
[66, 70], [67, 72], [68, 75], [69, 80]
]);
var container = document.getElementById('chart_div');
var chart = new google.visualization.LineChart(container);
var btnSave = document.getElementById('save-pdf');
google.visualization.events.addListener(chart, 'ready', function () {
btnSave.disabled = false;
});
btnSave.addEventListener('click', function () {
var doc = new jsPDF();
doc.addImage(chart.getImageURI(), 0, 0);
doc.save('chart.pdf');
}, false);
chart.draw(data, {
chartArea: {
bottom: 24,
left: 36,
right: 12,
top: 48,
width: '100%',
height: '100%'
},
height: 600,
title: 'chart title',
width: 600
});
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.5/jspdf.min.js"></script>
<input id="save-pdf" type="button" value="Save as PDF" disabled />
<div id="chart_div"></div>
You can use Mpdf to create pdf of google chart with store images,
Step 1. create.php
Use google method chart.getImageURI() to get image url then store into the variable after using jquery to submit form.
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script type="text/javascript">
google.charts.load("current", {packages:['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Element', 'Density', { role: 'style' }],
['Copper', 8.94, '#b87333', ],
['Silver', 10.49, 'silver'],
['Gold', 19.30, 'gold'],
['Platinum', 21.45, 'color: #e5e4e2' ]
]);
var options = {
title: "Density of Precious Metals, in g/cm^3",
bar: {groupWidth: '95%'},
legend: 'none',
};
// google chart 1
var g_chart_1 = document.getElementById('g_chart_1');
var g_chart_1 = new google.visualization.ColumnChart(g_chart_1);
g_chart_1.draw(data, options);
var chart_div = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(chart_div);
google.visualization.events.addListener(chart, 'ready', function () {
chart_div.innerHTML = '<img style="display:none" src="' + chart.getImageURI() + '" class="img-responsive">';
console.log(chart_div.innerHTML);
});
chart.draw(data, options);
// google chart 2
var g_chart_2 = document.getElementById('g_chart_2');
var g_chart_2 = new google.visualization.LineChart(g_chart_2);
g_chart_2.draw(data, options);
var chart_div1 = document.getElementById('chart_div1');
var chart1 = new google.visualization.LineChart(chart_div1);
google.visualization.events.addListener(chart1, 'ready', function () {
chart_div1.innerHTML = '<img style="display:none" src="' + chart1.getImageURI() + '" class="img-responsive">';
console.log(chart_div1.innerHTML);
});
chart1.draw(data, options);
}
</script>
<div class="container" id="Chart_details">
<div id='chart_div'></div><div id='g_chart_1'></div>
<div id='chart_div1'></div><div id='g_chart_2'></div>
</div>
<div align="center">
<form method="post" id="new_pdf" action="createchartpdf.php">
<input type="hidden" name="hidden_div_html" id="hidden_div_html" />
<button type="button" name="create_pdf" id="create_pdf" class="btn btn-danger btn-xs">Create PDF</button>
</form>
</div>
<script>
$(document).ready(function(){
$('#create_pdf').click(function(){
$('#hidden_div_html').val($('#Chart_details').html());
$('#new_pdf').submit();
});
});
</script>
</body>
</html>
step 2. createchartpdf.php
Get HTML data to get images url and store into the images folder, and then retrieve images and content.
print into pdf using mpdf. This is work with live server to print images.
<?php
if(isset($_POST["hidden_div_html"]) && $_POST["hidden_div_html"] != '')
{
$html = $_POST["hidden_div_html"];
$doc = new DOMDocument();
#$doc->loadHTML($html);
$tags = $doc->getElementsByTagName('img');
$i=1;
$result='';
foreach ($tags as $tag) {
$file_name = 'images/google_chart'.$i.'.png';
$img_Src=$tag->getAttribute('src');
file_put_contents($file_name, file_get_contents($img_Src));
$res= '<img src="images/google_chart'.$i.'.png">';
$result.=$res;
$i++;
}
//include make_pdf
include("mpdf60/mpdf.php");
$mpdf=new mPDF();
$mpdf->allow_charset_conversion = true;
$mpdf->SetDisplayMode('fullpage');
$mpdf->list_indent_first_level = 0; // 1 or 0 - whether to indent the first level of a list
$mpdf->WriteHTML($result);
$mpdf->Output();
}
?>

chart to pdf using echarts and jspdf

I’ve created a graph with echarts and want to include it to a pdf by using jspdf. I found that one way to do so might be to use canvas, transfer the graph to an image and finally include the image to the pdf. However, I fail to transfer the graph to an image. Here comes the code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Balken</title>
<script src="echarts.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.4/jspdf.debug.js"></script>
<div id="body">
<div id="chart"></div>
</div>
<!-- prepare a DOM container with width and height -->
<div id="main" style="width: 750px; height: 500px"></div>
<script type="text/javascript">
// based on prepared DOM, initialize echarts instance
var myChart = echarts.init(document.getElementById('main'));
// specify chart configuration item and data
var option = {
color: ['#3398DB'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: 'Salami',
type: 'bar',
barWidth: '60%',
data: [10, 52, 200, 334, 390, 330, 220]
}
]
};
// use configuration item and data specified to show chart
myChart.setOption(option);
var canvas = document.getElementById('main');
var dataURL = canvas.toDataURL();
//console.log(dataURL);
$('#exportButton').click(function () {
var pdf = new jsPDF();
pdf.addImage(dataURL, 'JPEG', 0, 0);
pdf.save('download.pdf');
});
</script>
<button id="exportButton" type="button">Export as PDF</button>
</body>
</html>
Any suggestions?
I needed this as well for a commercial product, so I did not give up until I found the solution.
You cannot use the ID of the chart to get the URL for the image, instead you need to search for the canvas.
($('canvas')[0]).toDataURL("image/png");
Notice the "[0]" means it will give your the first canvas, if you have more charts just do:
($('canvas')[0]).toDataURL("image/png");
($('canvas')[1]).toDataURL("image/png");
($('canvas')[2]).toDataURL("image/png");
3 Hours of searching and testing well spent :)
Enjoy!
I would use the toolbox, save as image:
.....
toolbox: {
feature: {
saveAsImage : {show: true}
}
}
.....
This option, among all the existing ones, will show you an icon to save the graphic as an image.
Quedaria así:
enter image description here
For more options with toolbox: http://echarts.baidu.com/echarts2/doc/option-en.html#title~toolbox
I hope it helps you.
You have to import "html2canvas" in order to make this work.
Html2canvas library will get the snapshot and that image should be written to the pdf with jspdf.
I have created a pen for this.
$("#exportButton").click(function(){
html2canvas($("#main"), {
onrendered: function(canvas) {
var dataURL=canvas.toDataURL('image/jpeg');
var pdf = new jsPDF();
pdf.addImage(dataURL, 'JPEG', 0, 0);
pdf.save("download.pdf");
}
});
});
Echart code:
<ReactEcharts
ref={(e) => {
this.echarts_react = e;
}}
option={option}
notMerge
lazyUpdate
/>
Function:
saveAsImage = (uri, name = 'undefine.jpeg') => {
var link = document.createElement('a');
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
};
saveAsPDF = (uri, name = 'undefine.pdf') => {
let height = echartsInstance.getHeight();
let width = echartsInstance.getWidth();
var doc = '';
if (width > height) {
doc = new jsPDF('l', 'mm', [width, height]);
} else {
doc = new jsPDF('p', 'mm', [height, width]);
}
doc.addImage(echartsInstance.getDataURL({ backgroundColor: '#fff' }), 'JPEG', 10, 10);
doc.save(name);
};
function call:
<li className="nav-item inline dropdown">
<span className="nav-link" data-toggle="dropdown">
<i className="fa fa-download" />
</span>
<div className="dropdown-menu dropdown-menu-scale pull-right">
<span
className="dropdown-item"
onClick={() =>
this.saveAsImage(this.echarts_react.getEchartsInstance().getDataURL({ backgroundColor: '#fff' }))
}>
Save as Image
</span>
<span
className="dropdown-item"
onClick={() =>
this.saveAsPDF(this.echarts_react.getEchartsInstance().getDataURL({ backgroundColor: '#fff' }))
}>
Save as PDF
</span>
</div>
</li>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.61/jspdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.5.0-beta1/html2canvas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.5.0-beta1/html2canvas.svg.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/amstockchart/3.13.0/exporting/rgbcolor.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.min.js"></script>
<script type="text/javascript">
// $("#list1").on("click",function(){
$("#list1").click(function(){
$("#row").html(option);
var imgData;
var svgElements = $("#row").find('svg');
//replace all svgs with a temp canvas
svgElements.each(function() {
var canvas, xml;
// canvg doesn't cope very well with em font sizes so find the calculated size in pixels and replace it in the element.
$.each($(this).find('[style*=em]'), function(index, el) {
$(this).css('font-size', getStyle(el, 'font-size'));
});
canvas = document.createElement("canvas");
canvas.className = "screenShotTempCanvas";
//convert SVG into a XML string
xml = (new XMLSerializer()).serializeToString(this);
// Removing the name space as IE throws an error
xml = xml.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/, '');
//draw the SVG onto a canvas
canvg(canvas, xml);
$(canvas).insertAfter(this);
//hide the SVG element
////this.className = "tempHide";
$(this).attr('class', 'tempHide');
$(this).hide();
});
/* html2canvas($("#row"), {
onrendered: function(canvas) {
var imgData = canvas.toDataURL(
'image/png');
var doc = new jsPDF('p', 'mm');
doc.addImage(imgData, 'PNG', 10, 10);
doc.save('sample-file.pdf');
}
});*/
var imgData;
html2canvas($("#row"), {
useCORS: true,
'allowTaint': true,
onrendered: function (canvas) {
imgData = canvas.toDataURL(
'image/jpeg', 1.0);
canvaswidth1=canvas.width/2;
canvasheight1=canvas.height/4;
currentHeight = $("#row").height();
currentHeight2=currentHeight/2;
var imgWidth = 200;
var pageHeight = 260;
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var doc = new jsPDF('p', 'm`enter code here`m','a4');
var position = 35;
doc.setFillColor(52,73,94);
doc.rect(5, 5, 200, 25, "F");
doc.setFontSize(40);
doc.setTextColor(255, 255, 255);
doc.text(80, 23, "Fitview");
doc.addImage(imgData, 'JPEG', 5, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
doc.addPage();
doc.addImage(imgData, 'JPEG', 5, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.save('healthcheck_Rapportage.pdf');
location.reload();
}
});
$("#row").find('.screenShotTempCanvas').remove();
$("#row").find('.tempHide').show().removeClass('tempHide');
});
</script>