how to initialize canvas and react it with vue - vuejs2

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>

Related

How to display an interactive graticule overlay in react native maps [duplicate]

What I'm trying to do is, created 2 markers (they are draggable), there will be grid between them, like http://prntscr.com/4nx9f3.
When I change one of the marker, grid should be changed. I am trying to draw with polylines. By the way i can not get latitude or longitute with marker1.getPosition().lat().
all my code:
<!DOCTYPE html>
<html>
<head>
<title>Simple Map</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script>
var map;
var marker1;
var marker2;
function initialize() {
var mapOptions = {
zoom: 10,
center: new google.maps.LatLng(50.3, 44.3)
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
marker1 = new google.maps.Marker({
position: new google.maps.LatLng(50.1, 44.1),
map: map,
draggable: true,
title: 'marker1 '
});
marker2 = new google.maps.Marker({
position: new google.maps.LatLng(50.5, 44.5),
map: map,
draggable: true,
title: 'marker2'
});
var flightPlanCoordinates = [
marker1.getPosition(),
marker2.getPosition(),
];
// code below is not working
/*
google.maps.event.addListener(marker1, 'dragend', function () {
polyline.LatLngBounds(new google.maps.LatLng(marker1.getPosition(), marker2.getPosition()));
});
google.maps.event.addListener(marker2, 'dragend', function () {
polyline.LatLngBounds(new google.maps.LatLng(marker1.getPosition(), marker2.getPosition()));
*/
var polyline = new google.maps.Polyline(
{ path: flightPlanCoordinates,
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: 1.0,
strokeWeight: 2
});
var lat1 = marker1.getPosition().lat();
var lng1 = marker1.getPosition().lng();
var lat2 = marker2.getPosition().lat();
var lng2 = marker2.getPosition().lng();
// I tried to get distance between 2 markers but it did not work either
/* function distance(
lat1,
lng1,
lat2,
lng2
) {
var R = 6371;
var a =
0.5 - Math.cos((lat2 - lat1) * Math.PI / 180) / 2 +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
(1 - Math.cos((lon2 - lon1) * Math.PI / 180)) / 2;
return R * 2 * Math.asin(Math.sqrt(a));
}
*/
polyline.setMap(map);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas"></div>
</body>
</html>
I think this is what you are trying to do. Changed your rectangleLng array to a two dimensional array and changed the excLat/excLng variables to not be rounded (they were backwards as well extLat wants to be up/down, excLng should be side to side).
proof of concept fiddle
code snippet:
var map;
var marker1;
var marker2;
var rectangle;
var infowindow = new google.maps.InfoWindow();
function initialize() {
var mapOptions = {
zoom: 11,
center: new google.maps.LatLng(38.4, 26.7)
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
marker1 = new google.maps.Marker({
position: new google.maps.LatLng(38.3, 26.6),
map: map,
draggable: true,
title: 'marker1'
});
google.maps.event.addListener(marker1, 'click', function(evt) {
infowindow.setContent(marker1.getPosition().toUrlValue(6));
infowindow.open(map, this);
});
marker2 = new google.maps.Marker({
position: new google.maps.LatLng(38.5, 26.8),
map: map,
draggable: true,
title: 'marker2'
});
google.maps.event.addListener(marker2, 'click', function(evt) {
infowindow.setContent(marker1.getPosition().toUrlValue(6));
infowindow.open(map, this);
});
rectangle = new google.maps.Rectangle({
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
map: map,
bounds: new google.maps.LatLngBounds(
marker1.getPosition(),
marker2.getPosition())
});
var leftSideDist = Math.round((marker2.getPosition().lng() - marker1.getPosition().lng()) * 10000) / 100;
var belowSideDist = Math.round((marker2.getPosition().lat() - marker1.getPosition().lat()) * 10000) / 100;
google.maps.event.addListener(marker1, 'dragend', function() {
rectangle.setBounds(new google.maps.LatLngBounds(marker1.getPosition(), marker2.getPosition()));
leftSideDist = Math.round((marker2.getPosition().lng() - marker1.getPosition().lng()) * 10000) / 100;
makeGrid();
});
google.maps.event.addListener(marker2, 'dragend', function() {
rectangle.setBounds(new google.maps.LatLngBounds(marker1.getPosition(), marker2.getPosition()));
belowSideDist = Math.round((marker2.getPosition().lat() - marker1.getPosition().lat()) * 10000) / 100;
makeGrid();
});
makeGrid();
}
var rectangleLat = [];
var rectangleLng = [];
function makeGrid() {
for (x in rectangleLng) {
for (y in rectangleLng[x]) {
if (rectangleLng[x][y].setMap) {
rectangleLng[x][y].setMap(null)
rectangleLng[x][y] = null;
}
}
}
var leftSideDist = marker2.getPosition().lng() - marker1.getPosition().lng();
var belowSideDist = marker2.getPosition().lat() - marker1.getPosition().lat();
var dividerLat = 5;
var dividerLng = 5; //ilerde kullanıcıdan alınacak
var excLat = belowSideDist / dividerLat;
var excLng = leftSideDist / dividerLng;
var m1Lat = marker1.getPosition().lat();
var m1Lng = marker1.getPosition().lng();
var m2Lat = marker2.getPosition().lat();
var m2Lng = marker2.getPosition().lng();
document.getElementById('info').innerHTML += "dividerLat=" + dividerLat + ", excLat=" + excLat + "<br>";
document.getElementById('info').innerHTML += "dividerLng=" + dividerLat + ", excLng=" + excLng + "<br>";
document.getElementById('info').innerHTML += "m1=" + marker1.getPosition().toUrlValue(6) + "<br>";
document.getElementById('info').innerHTML += "m2=" + marker2.getPosition().toUrlValue(6) + "<br>";
for (var i = 0; i < dividerLat; i++) {
if (!rectangleLng[i]) rectangleLng[i] = [];
for (var j = 0; j < dividerLng; j++) {
if (!rectangleLng[i][j]) rectangleLng[i][j] = {};
rectangleLng[i][j] = new google.maps.Rectangle({
strokeColor: '#FFFFFF',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.1,
map: map,
bounds: new google.maps.LatLngBounds(
new google.maps.LatLng(m1Lat + (excLat * i), m1Lng + (excLng * j)),
new google.maps.LatLng(m1Lat + (excLat * (i + 1)), m1Lng + (excLng * (j + 1))))
});
document.getElementById('info').innerHTML += "[i=" + i + ",j=" + j + "]:" + rectangleLng[i][j].getBounds() + "<br>";
} //for j Lng
} //for i Lat
document.getElementById('left').value = leftSideDist;
document.getElementById('blw').value = belowSideDist;
}
google.maps.event.addDomListener(window, 'load', initialize);
html,
body,
#map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
#panel {
position: absolute;
top: 5px;
left: 50%;
margin-left: -180px;
z-index: 5;
background-color: #fff;
padding: 5px;
/* border: 1px solid #999;*/
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<div id="map-canvas"></div>
<div id="panel" style="margin-left:-320px">leftSideDist:
<input type="text" readonly id="left">belowSideDist:
<input type="text" readonly id="blw">
</div>
<div id="info"></div>
To get the coordinates of the marker after it's been dragged, change:
google.maps.event.addListener(marker1, 'dragend', function() {
polyline.LatLngBounds(new google.maps.LatLng(marker1.getPosition(), marker2.getPosition()));
});
to:
google.maps.event.addListener(marker1, 'dragend', function(event) {
polyline.LatLngBounds(event.latLng);
});
However, there is no LatLngBounds function on the polyline object.
As far as i done:
<!DOCTYPE html>
<html>
<head>
<title>Simple Map</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
#panel {
position: absolute;
top: 5px;
left: 50%;
margin-left: -180px;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?
sensor=false&v=3&libraries=geometry"></script>
<script>
var map;
var marker1;
var marker2;
var rectangle;
function initialize() {
var mapOptions = {
zoom: 11,
center: new google.maps.LatLng(38.4, 26.7)
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
marker1 = new google.maps.Marker({
position: new google.maps.LatLng(38.3, 26.6),
map: map,
draggable: true,
title: 'marker1'
});
marker2 = new google.maps.Marker({
position: new google.maps.LatLng(38.5, 26.8),
map: map,
draggable: true,
title: 'marker2'
});
rectangle = new google.maps.Rectangle({
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
map: map,
bounds: new google.maps.LatLngBounds(
marker1.getPosition(),
marker2.getPosition())
});
var leftSideDist = Math.round((marker2.getPosition().lng() - marker1.getPosition().lng()) * 10000) / 100;
var belowSideDist = Math.round((marker2.getPosition().lat() - marker1.getPosition().lat()) * 10000) / 100;
google.maps.event.addListener(marker1, 'dragend', function () {
rectangle.setBounds(new google.maps.LatLngBounds(marker1.getPosition(), marker2.getPosition()));
leftSideDist = Math.round((marker2.getPosition().lng() - marker1.getPosition().lng()) * 10000) / 100;
});
google.maps.event.addListener(marker2, 'dragend', function () {
rectangle.setBounds(new google.maps.LatLngBounds(marker1.getPosition(), marker2.getPosition()));
belowSideDist = Math.round((marker2.getPosition().lat() - marker1.getPosition().lat()) * 10000) / 100;
});
var leftSideDist = Math.round((marker2.getPosition().lng() - marker1.getPosition().lng()) * 10000) / 100;
var belowSideDist = Math.round((marker2.getPosition().lat() - marker1.getPosition().lat()) * 10000) / 100;
var dividerLat = 5;
var dividerLng = 5; //ilerde kullanıcıdan alınacak
var excLat = leftSideDist / dividerLat;
var excLng = belowSideDist / dividerLng;
var rectangleLat[];
var rectangleLng[];
var m1Lat = marker1.getPosition().lat();
var m1Lng = marker1.getPosition().lng();
var m2Lat = marker2.getPosition().lat();
var m2Lng = marker2.getPosition().lng();
for (var i = 0; i < dividerLat; i++) {
for (var j = 0; j < dividerLng; j++) {
rectangleLng[i*5+j] = new google.maps.Rectangle({
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
map: map,
bounds: new google.maps.LatLngBounds(
( m1Lat , (excLng*(j+1) ) ),
( m1Lat+excLat, m2Lng+(excLng*(j+1) ) ) )
});
}//for j Lng
}//for i Lat
document.getElementById('left').value = leftSideDist;
document.getElementById('blw').value = belowSideDist;
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas"></div>
<div id="panel" style="margin-left:-320px">
leftSideDist: <input type="text" readonly id="left">
belowSideDist: <input type="text" readonly id="blw">
</div>
</body>
</html>

Create Konvajs Shapes and Connections creating dynamically based on button click events

I would like to create Rectangle Shapes and Connections using the Vue-Konva/Konvajs within my application. I do not want to create load the Static values rather I would like to create the Shapes when the user clicks on the Add Node button and create Connectors when the user clicks on the Add Connector button and build the connections between Shapes.
I looked into a few things and was able to do it using the mouse events but was unable to convert it to button clicks.
Following is the current code I have: CodeSandbox
Can someone please guide me on how to create shapes and connectors on click of the button events? Any suggestion or guidance is much appreciated.
I am looking something like this:
After trying a few things I was able to get it working. Posting here as it can be useful to someone in the future:
<template>
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<button class="btn btn-primary btn-sm" #click="addEvent()">
Add Event
</button>
<button class="btn btn-success btn-sm" #click="submitNodes()">
Submit
</button>
</div>
</div>
<div class="row root">
<div class="col-sm-12 body">
<v-stage
ref="stage"
class="stage"
:config="stageSize"
#mouseup="handleMouseUp"
#mousemove="handleMouseMove"
#mousedown="handleMouseDown"
>
<v-layer ref="layer">
<v-rect
v-for="(rec, index) in nodeArray"
:key="index"
:config="{
x: Math.min(rec.startPointX, rec.startPointX + rec.width),
y: Math.min(rec.startPointY, rec.startPointY + rec.height),
width: Math.abs(rec.width),
height: Math.abs(rec.height),
fill: 'rgb(0,0,0,0)',
stroke: 'black',
strokeWidth: 3,
}"
/>
</v-layer>
</v-stage>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
stageSize: {
width: null,
height: 900
},
lines: [],
isDrawing: false,
eventFlag: false,
nodeCounter: 0,
nodeArray: []
}
},
mounted () {
if (process.browser && window !== undefined) {
this.stageSize.width = window.innerWidth
// this.stageSize.height = window.innerHeight
}
},
methods: {
handleMouseDown (event) {
if (this.eventFlag) {
this.isDrawing = true
const pos = this.$refs.stage.getNode().getPointerPosition()
const nodeInfo = this.nodeArray[this.nodeArray.length - 1]
nodeInfo.startPointX = pos.x
nodeInfo.startPointY = pos.y
console.log(JSON.stringify(nodeInfo, null, 4))
}
},
handleMouseUp () {
this.isDrawing = false
this.eventFlag = false
},
setNodes (element) {
this.nodeArray = element
},
handleMouseMove (event) {
if (!this.isDrawing) {
return
}
// console.log(event);
const point = this.$refs.stage.getNode().getPointerPosition()
// Handle rectangle part
const curRec = this.nodeArray[this.nodeArray.length - 1]
curRec.width = point.x - curRec.startPointX
curRec.height = point.y - curRec.startPointY
},
// Function to read the Nodes after add all the nodes
submitNodes () {
console.log('ALL NODE INFO')
console.log(JSON.stringify(this.nodeArray, null, 4))
this.handleDragstart()
},
addEvent () {
this.eventFlag = true
this.setNodes([
...this.nodeArray,
{
width: 0,
height: 0,
draggable: true,
name: 'Event ' + this.nodeCounter
}
])
this.nodeCounter++
}
}
}
</script>
<style scoped>
.root {
--bg-color: #fff;
--line-color-1: #D5D8DC;
--line-color-2: #a9a9a9;
}
.body {
height: 100vh;
margin: 0;
}
.stage {
height: 100%;
background-color: var(--bg-color);
background-image: conic-gradient(at calc(100% - 2px) calc(100% - 2px),var(--line-color-1) 270deg, #0000 0),
conic-gradient(at calc(100% - 1px) calc(100% - 1px),var(--line-color-2) 270deg, #0000 0);
background-size: 100px 100px, 20px 20px;
}
</style>

How to animate svg elements in a for loop with vue?

working with vue-konva (svg based canvas library) right now. I'm trying to animate all shapes which are defined by a v-loop. While trying to use the Konva Animation library functions, I'm getting "Cannot read property 'getNode' of undefined" error. I'm assuming this is due to the fact that a ref has to have one specific element and not be adressed inside a v-for loop. How can I simultaneaously animate all the polygons?
SVG / canvas element:
<v-regular-polygon
v-for="el in results"
ref="hexagon"
:key="el.index"
:config="{
x: 200 * Math.abs(el.land),
y: 200,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
}"
/>
function responsible for animation
mounted() {
this.fetchTemperature()
const vm = this
const amplitude = 100
const period = 5000
// in ms
const centerX = vm.$refs.stage.getNode().getWidth() / 2
const hexagon = this.$refs.hexagon.getNode()
// example of Konva.Animation
const anim = new Konva.Animation(function (frame) {
hexagon.setX(amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX)
}, hexagon.getLayer())
anim.start()
},
You can set a unique ref to every polygon by adding an index.
<v-regular-polygon
v-for="(el, i) in results"
:ref="`hexagon_${i}`"
...
Here's an example:
<template>
<div id="app">
<v-stage :config="configKonva">
<v-layer>
<v-circle
v-for="(item, i) in items"
:config="item"
:key="i"
:ref="`circle_${i}`"
></v-circle>
</v-layer>
</v-stage>
</div>
</template>
<script>
import Konva from "konva";
export default {
name: "App",
data() {
return {
items: [
{
x: 30,
y: 100,
radius: 20,
fill: "red",
stroke: "black",
strokeWidth: 2,
},
{
x: 100,
y: 100,
radius: 20,
fill: "red",
stroke: "black",
strokeWidth: 2,
},
],
configKonva: {
width: 200,
height: 200,
},
};
},
mounted() {
for (let i = 0; i < this.items.length; i++) {
const node = this.$refs[`circle_${i}`][0].getNode();
const period = 1000;
const amplitude = 10;
const anim = new Konva.Animation((frame) => {
node.setX(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
this.items[i].x
);
}, node.getLayer());
anim.start();
}
},
};
</script>
Codesandbox

Set element height based on parent height in vue js on page load

I want to set the height of a vue component (to be specific it is this -> https://github.com/jbaysolutions/vue-grid-layout).
The height of which (in vue-grid-layout -> grid-item) should be same as of its parent.
And this also should be only in page load time. So how this can be achieved in vue js?
I don't want to do this using CSS. I need height in pixels. as to vue-grid-layout -> item height needs in pixel initially. as it is resizable, it can be changed afterwards.
it would be easier to provide an accurate answer with some example html (your actual code), but the following should work in general.
export default {
...
data() {
return {
parentHeight: 0
}
},
mounted() {
this.parentHeight = this.$parent.$el.offsetHeight;
}
...
}
So in the mounted lifecycle hook you can read the height of the parent and then set it where ever you need it.
No need for advanced javascript to calculate the height, just use styling:
height: 100%;
Demo:
.parent {
resize: both;
overflow: auto;
height: 100px;
display: block;
width: 100px;
border: 1px solid black;
}
.child {
height: 100%;
background: pink;
}
<div class="parent">
</div>
<div class="parent">
<div class="child">
I'm a child!
</div>
</div>
I found a solution to fix the grid-layout height depending on available space. For that I have used folowing props: max-rows, row-height, margins, autoSize=false
And ResizeObserver which will adjust grid-layout row-height according to available height after window resize. Also be aware that I used some Bootstrap classes for styling
<template>
<div class="flex-grow-1 w-100">
<grid-layout
:layout="layout"
:col-num="colCount"
:maxRows="rowCount"
:row-height="rowHeight"
:autoSize="false"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:preventCollision="true"
:vertical-compact="false"
:margin="margin"
:use-css-transforms="true"
>
<grid-item
v-for="item in layout"
class="bg-primary"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
>
<span class="remove" #click="removeItem(item.i)">x</span>
</grid-item>
</grid-layout>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/runtime-core';
interface GridItem {
x: number;
y: number;
w: number;
h: number;
i: string;
}
interface State {
layout: GridItem[];
index: number;
colCount: number;
rowCount: number;
rowHeight: number;
observer: null | ResizeObserver;
margin: number[];
}
export default defineComponent({
name: 'VideoWall',
data(): State {
return {
layout: [
{ x: 0, y: 0, w: 2, h: 2, i: '0' },
{ x: 2, y: 0, w: 2, h: 4, i: '1' },
{ x: 4, y: 0, w: 2, h: 5, i: '2' },
{ x: 6, y: 0, w: 2, h: 3, i: '3' },
{ x: 8, y: 0, w: 2, h: 3, i: '4' },
],
index: 0,
colCount: 36,
rowCount: 36,
rowHeight: 40,
margin: [5, 5],
observer: null,
};
},
mounted() {
this.observer = new ResizeObserver(this.onResize);
if (this.$el instanceof Element) {
this.observer.observe(this.$el);
} else {
console.log('VideoWall: Failed to bind observer');
}
},
unmounted() {
if (this.observer) {
this.observer.disconnect();
}
},
methods: {
onResize(entries: ResizeObserverEntry[]): void {
this.rowHeight =
Math.floor(entries[0].contentRect.height / this.rowCount) -
this.margin[1];
},
},
});
</script>

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>