In Django's template, how to access a element in a nested loop by index - django-templates

Project: I create sudoku 9x9 using Django. After user input value, I will response the result.For user easy to understand, the input element and resolved element will be displayed at different color.
Problem: Try to access the element in a nested loop by index (like matrix_result.row.col), but it does not work.
In views.py has successfully pass to matrix: matrix_input, matrix_result
matrix_input1 = [[5, 7, 0, 3, 6, 4, 2, 9, 8],
[6, 3, 0, 7, 9, 2, 4, 1, 5],
[9, 4, 2, 0, 1, 5, 3, 0, 7],
[3, 2, 4, 0, 5, 8, 6, 7, 9],
[8, 6, 9, 2, 0, 7, 5, 0, 1],
[7, 1, 5, 6, 0, 9, 8, 3, 2],
[1, 8, 6, 9, 2, 0, 7, 5, 4],
[2, 5, 3, 4, 7, 0, 9, 8, 6],
[4, 9, 7, 5, 8, 6, 1, 0, 3]]
matrix_result1 = [[5, 3, 7, 1, 6, 4, 2, 9, 8],
[6, 3, 4, 7, 9, 2, 4, 1, 5],
[9, 4, 2, 7, 1, 5, 3, 0, 7],
[3, 2, 4, 9, 5, 8, 6, 7, 9],
[8, 6, 9, 2, 4, 7, 5, 0, 1],
[7, 1, 5, 6, 2, 9, 8, 3, 2],
[1, 8, 6, 9, 2, 8, 7, 5, 4],
[2, 5, 3, 4, 7, 9, 9, 8, 6],
[4, 9, 7, 5, 8, 6, 1, 2, 3]]
return render(request,'sudoku_app/display_sloved_result.html', {'matrix_result1': matrix_result1, 'matrix_input1': matrix_input1})
In display_sloved_result.html, the below code was be processed.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hotel_image</title>
</head>
<body>
<style>
table { border-collapse: collapse; font-family: Calibri, sans-serif; text-align: center;}
colgroup, tbody { border: solid medium; }
td {
border: solid thin;
height: 45px;
width: 45px;
text-align: center;
padding: 0;
font-size: x-large;
font-weight: 500px;
}
.trx{
border-bottom: solid medium;
}
</style>
<h1>This is the Solved Result</h1>
<table>
<colgroup><col><col><col></colgroup>
<colgroup><col><col><col></colgroup>
<colgroup><col><col><col></colgroup>
{% for row in matrix_input1 %}
{% if forloop.counter0 == 2 or forloop.counter0 == 5 %}
<tr class="trx">
{% else %}
<tr>
{% endif %}
{% for col in row %}
{% if col != 0 %}
<td> {{ col }} </td>
{% else %}
<td>{{matrix_result1.forloop.parentloop.counter0.forloop.counter0}}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</table>
</body>
</html>
Result:
The result is display as attached image .
The problem is
{{matrix_result1.forloop.parentloop.counter0.forloop.counter0}}
do not reply any value.
Question: It is possible to access element in nested loop by index in Django's template

Related

TensorFlow multi class training and prediction

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.

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();
}
?>

Google Charts Tools - Scatter and Table Combo Interaction, why unidirectional?

This is my first question on stack overflow. I made a google chart tools scatter chart/table combination that is based on their examples. When I click a row in the table, the corresponding point in the scatter is selected (that's good). But it is not working in reverse. I cannot select a point in the scatter and have it select the corresponding row in the table. Does anyone know why this does not work as expected? my code is provided below:
<html>
<head>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart", "table"]});
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Age', 'Weight'],
[ 8, 12],
[ 4, 5.5],
[ 11, 14],
[ 4, 5],
[ 3, 3.5],
[ 6.5, 7],
[ 6.5, 7]
]);
var options = {
title: 'Age vs. Weight comparison',
hAxis: {title: 'Age', minValue: 0, maxValue: 15},
vAxis: {title: 'Weight', minValue: 0, maxValue: 15},
legend: 'none'
};
var chart = new google.visualization.ScatterChart(document.getElementById('chart_div'));
var table = new google.visualization.Table(document.getElementById('table_div'));
chart.draw(data, options);
table.draw(data);
google.visualization.events.addListener(chart, 'select', function() {
table.setSelection(chart.getSelection());});
google.visualization.events.addListener(table, 'select', function() {
chart.setSelection(table.getSelection());});
}
google.setOnLoadCallback(drawChart);
</script>
</head>
<body>
<div id="chart_div" style="width: 500px; height: 500px;"></div>
<div id="table_div" style="width: 500px; height: 500px;"></div>
</body>
</html>
Because getSelection() returns both the column and row for the scatter graph and just row for the table, there's a bit of a conflict. Turns out, if you just pass the row information from the scatter graph, you can indeed select the table row, you just need the following:
table.setSelection([{row: chart.getSelection()[0].row}]);
That takes the first item you've clicked on and passes an object with just row data.
Inspiration from here and working example here.