Now i have choice of different colors, but when selecting other color, line is still drawn from last point of latest line. How could i draw in new fresh place after choosing other color?
function drawLine() {
canvas.on("mouse:down", function(event) {
var pointer = canvas.getPointer(event.e);
var positionX = pointer.x;
var positionY = pointer.y;
var circlePoint = new fabric.Circle({
radius: 1,
fill: pipeColor,
left: positionX,
top: positionY,
selectable: false,
originX: "center",
originY: "center",
hoverCursor: "auto"
});
canvas.add(circlePoint);
// Store the points to draw the lines
pipePoints.push(circlePoint);
// console.log(pipePoints);
if (pipePoints.length > 1) {
var startPoint = pipePoints[pipePoints.length - 2];
var endPoint = pipePoints[pipePoints.length - 1];
var pipeLine = new fabric.Line(
[
startPoint.get("left"),
startPoint.get("top"),
endPoint.get("left"),
endPoint.get("top")
],
{
stroke: pipeColor,
strokeWidth: 2,
hasControls: false,
hasBorders: false,
selectable: false,
lockMovementX: true,
lockMovementY: true,
hoverCursor: "default",
originX: "center",
originY: "center"
}
);
pipeLines.push(pipeLine);
canvas.add(pipeLine);
}
});
$('#colorpicker').change(function () {
canvas.item(0).selectable = false;
pipeColor = $(this).val();
drawLine();
});
}
For now I can draw any lines i want starting at point X and finishing at point Y, but when i change color of line i wanna start at point Z not point Y
It is not much FabricJS related question. It is more about the implementation and the needs of the app. It can vary cause of requirements and the programmer's experience. Anyway, here is an example width a demo how you can potentially draw multiple lines and go back to the previous lines and continue editing them. You can check the inline StackOverflow snipper or check it on CodeSandbox:
https://codesandbox.io/s/stackoverflow-60753858-fabric-js-1720-6ke80
var canvas = new fabric.Canvas("canvas");
var currentLine = "water";
document
.getElementsByClassName("options")[0]
.addEventListener("click", function(e) {
if (e.target.classList.contains("line-type")) {
// Remove active class from previous element
document
.querySelector(".line-type.is-active")
.classList.remove("is-active");
e.target.classList.add("is-active");
currentLine = e.target.dataset.lineType;
console.warn("Current line: " + currentLine);
}
});
var linesData = {
water: {
linePoints: [],
lineLines: [],
color: "blue"
},
electricity: {
linePoints: [],
lineLines: [],
color: "yellow"
},
internet: {
linePoints: [],
lineLines: [],
color: "gray"
}
};
canvas.on("mouse:down", function(event) {
var pointer = canvas.getPointer(event.e);
var positionX = pointer.x;
var positionY = pointer.y;
// Add small circle as an indicative point
var circlePoint = new fabric.Circle({
radius: 5,
fill: linesData[currentLine].color,
left: positionX,
top: positionY,
selectable: false,
originX: "center",
originY: "center",
hoverCursor: "auto"
});
canvas.add(circlePoint);
// Store the points to draw the lines
linesData[currentLine].linePoints.push(circlePoint);
if (linesData[currentLine].linePoints.length > 1) {
// Just draw a line using the last two points, so we don't need to clear
// and re-render all the lines
var startPoint =
linesData[currentLine].linePoints[
linesData[currentLine].linePoints.length - 2
];
var endPoint =
linesData[currentLine].linePoints[
linesData[currentLine].linePoints.length - 1
];
var waterLine = new fabric.Line(
[
startPoint.get("left"),
startPoint.get("top"),
endPoint.get("left"),
endPoint.get("top")
],
{
stroke: linesData[currentLine].color,
strokeWidth: 4,
hasControls: false,
hasBorders: false,
selectable: false,
lockMovementX: true,
lockMovementY: true,
hoverCursor: "default",
originX: "center",
originY: "center"
}
);
linesData[currentLine].lineLines.push(waterLine);
canvas.add(waterLine);
}
});
canvas.renderAll();
document
.getElementById("clear-water-pipe")
.addEventListener("click", function(e) {
linesData[currentLine].linePoints.forEach(function(point) {
canvas.remove(point);
});
linesData[currentLine].linePoints = [];
linesData[currentLine].lineLines.forEach(function(line) {
canvas.remove(line);
});
linesData[currentLine].lineLines = [];
});
body {
font-family: sans-serif;
}
canvas {
border: 2px solid #333;
}
button {
margin-top: 10px;
padding: 10px;
border: 1px solid #555;
cursor: pointer;
}
.options {
padding-top: 10px;
}
.lines {
display: flex;
align-items: center;
}
.lines > div {
border: 2px solid #333;
color: white;
margin-right: 10px;
padding: 10px;
}
.lines > div.is-active {
border: 5px solid red;
}
.water-pipe {
background-color: blue;
}
.lines .electricity-line {
background-color: yellow;
color: #555;
}
.internet-line {
background-color: gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<div id="app">
<canvas id="canvas" width="500" height="350"></canvas>
<div class="options">
<div class="lines" id="lines">
<div class="line-type water-pipe is-active" data-line-type="water">
Water pipe
</div>
<div class="line-type electricity-line" data-line-type="electricity">
Electricity line
</div>
<div class="line-type internet-line" data-line-type="internet">
Internet line
</div>
</div>
<button id="clear-water-pipe">Clear active line</button>
</div>
</div>
Related
Hello guys I would like to know how to add percentage like image below.
I want to add text which contain the percentage of each data.
It's gonna be between pie-chart and legend.
How to add the text or maybe change the style of legend by adding text on the above of legends.
What I've done so far
HTML
<div class="block__chart">
<div id="counter"></div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
CSS
* {
padding: 0;
margin: 0;
font-family: "Montserrat", sans-serif;
}
.block__chart {
width: 100%;
background-color: #eee;
padding: 2em;
display: flex;
flex-direction: column;
}
#myChart {
width: 100%;
margin: 1em 0;
}
canvas {
width: 452px;
}
#counter {
text-align: center;
font-size: 2em;
font-weight: 700;
color: #007cbd;
p {
color: black;
font-weight: 400;
}
}
.activeUser {
font-size: 20px;
}
.lastFiveMins {
font-size: 10px;
}
JS
// Fake Data - Total 46946
var dataset = [
{ label: "Mobile", count: 18778, color: "#72bbe1" },
{ label: "Desktop", count: 28168, color: "#3ea6dd" }
];
var dataTotal = dataset.reduce((acc, data) => (acc += data.count), 0);
var counter = document.getElementById("counter");
counter.innerHTML = `
${dataTotal}
<p class="activeUser">ACTIVE USER</p>
<p class="lastFiveMins">IN THE LAST 5 MINUTES</p>`;
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: "pie",
data: {
labels: [dataset[0].label.toUpperCase(), dataset[1].label.toUpperCase()],
datasets: [
{
data: [dataset[0].count, dataset[1].count],
backgroundColor: [dataset[0].color, dataset[1].color],
borderWidth: 1
}
]
},
options: {
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
let allData = data.datasets[tooltipItem.datasetIndex].data;
let sumData = allData.reduce((memo, data) => (memo += data), 0);
let tooltipLabel = data.labels[tooltipItem.index];
let tooltipData = allData[tooltipItem.index];
let tooltipPercentageCalc = (tooltipData / sumData) * 100;
let tooltipPercentage = `${tooltipPercentageCalc.toFixed(1)}%`;
return `${tooltipLabel} : ${tooltipData} (${tooltipPercentage})`;
}
}
},
legend: {
display: true,
labels: {
usePointStyle: true,
text: "Hello World",
fontColor: "black",
fontSize: 15
},
position: "bottom"
}
}
});
you can use filter options for customizing your labels
example down below
legend: {
display: true,
labels: {
usePointStyle: true,
text: "Hello World",
fontColor: "black",
fontSize: 15,
filter: function(legendItem, data) {
let labels = data.labels, datasets = data.datasets[0].data;
for(let i=0;i<labels.length;i++){
if(labels[i].indexOf(legendItem.text)!=-1){
let t = legendItem.text;
legendItem.text = t+' : '+datasets[i] + '%';
break;
}
}
return legendItem;
},
},
position: "bottom"
}
I am trying to merge textbox and group in fabricjs
when I set text, It doesn't show full text.
how to set full text?
var iText4 = new fabric.Textbox('Text noasasasasasasasasasabcdefghxyz', {
left: 50,
top: 100,
fontFamily: 'Helvetica',
width: 30,
styles: {
0: {
0: { textBackgroundColor: 'blue', fill: 'green' },
1: { textBackgroundColor: '#faa' },
2: { textBackgroundColor: 'lightblue' },
}
}
});
var group = new fabric.Group([ iText4 ], {
left: 150,
top: 100,
width: 60,
});
var canvas = new fabric.Canvas('c');
canvas.add(group);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id='c' width='500' height='400'></canvas>
The group's width is smaller than the text's width, causing it to be cut off. Removing it should solve your problem.
var group = new fabric.Group([ iText4 ], {
left: 150,
top: 100
});
See here: https://jsfiddle.net/p6c2trg8/1/
i have a question - how to draw canvas layer (for example just simple square) with event on click on button in Vue.js? I have stage and on that stage with position x:0, y:0 i want after click on button to generate that square and with drag and drop to position it on that stage? I'm using Konvajs for creating Canvas
Can somebody help me?
<template>
<div id="main">
<h1></h1>
<div id="obszarroboczy" style="width: 500px; height: 600px;">
<v-stage ref="stage"
:config="configKonva"
#dragstart="handleDragstart"
#dragend="handleDragend">
<v-layer ref="layer">
<v-star
v-for="item in list"
:key="item.id"
:config="item"></v-star>
</v-layer>
<v-layer ref="dragLayer"></v-layer>
</v-stage>
</div>
<div class="col-md-6">
<button v-on:click="handleClick" id="more_canvas">More</button>
</div>
</div>
</template>
<script>
import Vue from "vue";
import axios from "axios";
import draggable from "vuedraggable";
import swal from "sweetalert2";
import VueKonva from "vue-konva";
export default {
name: "EnumCurrencyIndex",
$mount: "#main",
components: {
draggable
},
data() {
return {
model: [],
editable: true,
isDragging: false,
delayedDragging: false,
type: "currency",
editedElement: null,
newElement: "",
list: [],
configKonva: {
width: 400,
height: 400
},
configCircle: {
x: 100,
y: 100,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
},
vm: {}
};
},
beforeMount() {
this.fetchData();
},
computed: {
dragOptions() {
return {
animation: 0,
group: "description",
disabled: !this.editable,
ghostClass: "ghost"
};
},
listString() {
return this.model;
},
dragCanvas() {
return this.model;
}
},
watch: {
$route: "fetchData",
isDragging(newValue) {
if (newValue) {
this.delayedDragging = true;
return;
}
this.$nextTick(() => {
this.delayedDragging = false;
});
}
},
methods: {
handleDragstart(starComponent) {
var vm = this;
const shape = starComponent.getStage();
const dragLayer = vm.$refs.dragLayer.getStage();
const stage = vm.$refs.stage.getStage();
// moving to another layer will improve dragging performance
shape.moveTo(dragLayer);
stage.draw();
starComponent.config.shadowOffsetX = 15;
starComponent.config.shadowOffsetY = 15;
starComponent.config.scaleX = starComponent.config.startScale * 1.2;
starComponent.config.scaleY = starComponent.config.startScale * 1.2;
},
handleDragend(starComponent) {
var vm = this;
const shape = starComponent.getStage();
const layer = vm.$refs.layer.getStage();
const stage = vm.$refs.stage.getStage();
shape.moveTo(layer);
stage.draw();
shape.to({
duration: 0.5,
easing: Konva.Easings.ElasticEaseOut,
scaleX: starComponent.config.startScale,
scaleY: starComponent.config.startScale,
shadowOffsetX: 5,
shadowOffsetY: 5
});
},
handleClick(configCircle) {
var vm = this;
const shape = vm.$refs.layer.getStage();
const layer = vm.$refs.layer.getStage();
const stage = vm.$refs.stage.getStage();
console.log(1);
layer.add(configCircle);
stage.add(layer);
},
haveIntersection(r1, r2) {
return !(
r2.x > r1.x + r1.width ||
r2.x + r2.width < r1.x ||
r2.y > r1.y + r1.height ||
r2.y + r2.height < r1.y
);
},
orderList() {
this.model = this.model.sort((one, two) => {
return one.position - two.position;
});
},
onMove({ relatedContext, draggedContext }) {
const relatedElement = relatedContext.element;
const draggedElement = draggedContext.element;
return (
(!relatedElement || !relatedElement.fixed) && !draggedElement.fixed
);
},
fetchData() {
var vm = this;
axios
.get(`/api/${this.resource}?type=${this.type}`)
.then(function(response) {
Vue.set(vm.$data, "model", response.data.model);
})
.catch(function(error) {
console.log(error);
});
}
},
mounted() {
var box = document.getElementById("obszarroboczy");
this.configKonva.width = box.offsetWidth;
this.configKonva.height = box.offsetHeight;
var vm = this;
for (let n = 0; n < 30; n++) {
const scale = Math.random();
const stage = vm.$refs.stage.getStage();
vm.list.push({
x: Math.random() * stage.getWidth(),
y: Math.random() * stage.getHeight(),
rotation: Math.random() * 180,
numPoints: 5,
innerRadius: 30,
outerRadius: 50,
fill: "#89b717",
opacity: 0.8,
draggable: true,
scaleX: scale,
scaleY: scale,
shadowColor: "black",
shadowBlur: 10,
shadowOffsetX: 5,
shadowOffsetY: 5,
shadowOpacity: 0.6,
startScale: scale
});
};
},
directives: {
"element-focus": function(el, binding) {
if (binding.value) {
el.focus();
}
}
}
};
</script>
<style>
#obszarroboczy {
width: 100px;
height: 300px;
}
.normal {
background-color: grey;
}
.table td {
width: 100px;
height: 100px;
background: white;
border: 2px dotted black;
max-width: 100px;
padding: 5px;
}
.drag {
display: flex;
flex-direction: row;
}
.list {
flex-grow: 1;
max-width: 47%;
margin-right: 40px;
}
.name {
width: 50%;
display: inline-block;
height: 50px;
background: pink;
border: 5px green solid;
box-sizing: border-box;
padding: 5px;
}
.name.large {
width: 100%;
}
.dragArea {
min-height: 100px;
}
.dragArea img {
margin: 3px;
cursor: pointer;
}
</style>
var mainCanvas = new Vue({
el: '#main', // the element where the method wil lrender the canvas to
data: {
name: 'Vue.js'
},
methods: {
handleClick: function (event) { // handleClick is the method name for the button
var stage = new Konva.Stage({ // this line till the stage.add() line renders the draggable square
container: 'obszarroboczy',
width: 500,
height: 500
});
var layer = new Konva.Layer();
var rect = new Konva.Rect({
x: 0,
y: 0,
width: 100,
height: 100,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
layer.add(rect);
stage.add(layer);
}
}
});
I added comments to explain what certain important lines does but you can check out the official KonvaJS Docs in GitHub for a more detailed explanation on what each line above does.
I try to add label when user hover element:
var rect = new fabric.Rect({
originX: 'top',
originY: 'top',
width: 150,
height: 120,
fill: 'rgba(255,0,0,0.5)',
transparentCorners: true
});
var text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'top',
originY: 'top'
});
canvas.on('mouse:over', function(e) {
var group = new fabric.Group([ rect, text ], {
left: e.target.left,
top: e.target.top
});
canvas.add(group);
canvas.renderAll();
});
canvas.on('mouse:out', function(e) {
//e.target.set('fill', 'green');
canvas.remove(group);
canvas.renderAll();
});
But when mouse:out fires i get:
Uncaught ReferenceError: group is not defined
at i.<anonymous> (can.js:38)
at i.r (fabric.min.js:1)
at i._fireOverOutEvents (fabric.min.js:3)
at i.findTarget (fabric.min.js:3)
at i.__onMouseMove (fabric.min.js:4)
at i._onMouseMove (fabric.min.js:4)
How can i make a group global with left and top of hovered element, or there is a better way to do that?
group is scoped as a private variable local to the mouse:over event. Try
removing var from:
var group = new fabric.Group([ rect, text ], {
to scope group globally:
group = new fabric.Group([ rect, text ], {
DEMO
var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
originX: 'top',
originY: 'top',
width: 150,
height: 120,
fill: 'rgba(255,0,0,0.5)',
});
var text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'top',
originY: 'top'
});
var group = new fabric.Group([ rect, text ], {
left: 0,
top: 0,
selectable : false,
visible: false,
});
canvas.add(group);
canvas.renderAll();
canvas.on('mouse:move', function(e) {
var p = canvas.getPointer(e.e);
group.set({
left: p.x,
top: p.y,
visible: true
});
canvas.renderAll();
});
canvas.on('mouse:out', function(e) {
group.set({
visible: false
})
canvas.renderAll();
});
canvas {
border:2px dotted blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
<canvas id='c' width=400 height=400></canvas>
You no need to create group on every mouse over, you just set visible: true/false, so according to this it will visible. Check DEMO.
I am creating a list (ScrollView) with some custom "rows" (The image contains a single "row"). I show 5 rows and have added an event listener so that when the user scrolls to the end, 5 more elements are loaded and displayed. I made this using Alloy but I noticed it was taking too long so I tried writing the views manually.
// created the views programmatically to see if there was any difference from Alloy
function createRow(args) {
var container = Ti.UI.createView({
layout: "vertical",
width: Ti.UI.FILL,
height: "42dp"
});
var rowContent = Ti.UI.createView({
width: Titanium.UI.FILL,
height: "41dp", //Titanium.UI.FILL,
layout: "horizontal",
left: "16dp",
right: "16dp"
});
var border = Ti.UI.createView({
left: "16dp",
right: "16dp",
height: "1dp",
backgroundColor: Colors.darkGrey
});
var titleScroll = Ti.UI.createScrollView({
scrollType: "horizontal",
width: "49%",
horizontalWrap: false
});
var scrollContainer = Ti.UI.createScrollView({
scrollType: "horizontal",
horizontalWrap: false,
width: "50%"
});
var scroll = Ti.UI.createView({
layout: "horizontal",
horizontalWrap: false,
right: 0,
width: Ti.UI.SIZE,
height: Titanium.UI.SIZE
});
var title = Ti.UI.createLabel({
text: args.title,
font: args.isTitle ? {font: "Lato-Regular", fontSize: "22dp"} : {fontFamily: "Lato-Regular", fontSize: "15"},
horizontalWrap: false,
wordWrap: false,
left: 0,
color: Colors.grey,
minimumFontSize: "15dp"
});
if(args.value) {
var t = args.value.join();
scroll.add(Ti.UI.createLabel({
text: t,
color: args.action ? Colors.blue : Colors.black,
font: {fontSize: "15dp", fontFamily: "Lato-Regular"},
right: "5dp",
width: Ti.UI.SIZE,
horizontalWrap: false,
wordWrap: false,
minimumFontSize: "15dp"
}));
}
if(args.data)
scrollContainer.data = args.data; //just a dump of the data used by the click handler
if(args.action)
scrollContainer.addEventListener("click",args.action);
scrollContainer.add(scroll);
titleScroll.add(title);
rowContent.add(titleScroll);
rowContent.add(scrollContainer);
container.add(rowContent);
container.add(border);
return container; //Ti.UI.View
}
function createHeader(args) {
var header = Ti.UI.createView({
layout: "horizontal",
height: "44dp",
backgroundColor: "#fff"
});
var leftView = Ti.UI.createView({
width: "25%",
height: Ti.UI.FILL
});
var rightView = Ti.UI.createView({
width: "25%",
height: Ti.UI.FILL
});
var centerView = Ti.UI.createView({
width: "49%",
height: Ti.UI.FILL
});
var verticalAligner = Ti.UI.createView({
height: Ti.UI.SIZE,
width: Ti.UI.SIZE,
layout: "vertical"
});
var headerTitle = Ti.UI.createLabel({
color: Colors.green,
font: {fontSize: "16.5dp", fontFamily: "Lato-Regular"},
textAlign: "center",
horizontalWrap: false,
wordWrap: false
});
var headerSubtitle = Ti.UI.createLabel({
font: {fontSize: "14dp", fontFamily: "Lato-Regular"},
textAlign: "center",
color: Colors.grey,
horizontalWrap: false,
wordWrap: false
});
if(args.rightView)
rightView.add(args.rightView);
if(args.leftView)
leftView.add(args.leftView);
verticalAligner.add(headerTitle);
verticalAligner.add(headerSubtitle);
centerView.add(verticalAligner);
header.add(leftView);
header.add(centerView);
header.add(rightView);
headerTitle.text = args.title;
headerSubtitle.text = args.subTitle;
return header;
}
function createBlock(args) {
var container = Ti.UI.createView({
layout: "vertical",
width: "100%",
height: Ti.UI.SIZE
});
var covers = Ti.UI.createView({ //
height: "119dp"
});
var content = Ti.UI.createView({
height: Ti.UI.SIZE,
layout: "vertical"
});
function goToEvent() {
Storage.event.id = args.event;
Alloy.Globals.openWin("event");
}
var data = new D.data();
var w = Android ? Ti.Platform.displayCaps.platformWidth : Measure.dpToPX(Ti.Platform.displayCaps.platformWidth);
var h = Measure.dpToPX(119); //Android ? Alloy.Globals.dpToPX(119) : Measure.dpToPX(119);
if(args.images) {
//setTimeout(function() { //timeout didn't make any difference
var image = null;
//for(var i = 0; i < args.images.length; i++) {
image = Ti.UI.createImageView({
image: data.getBlobResized({ //returns a URL for the picture
id: args.images[0], //i
width: w,
height: h
}),
width: iOS ? Measure.pxToDP(w) : Alloy.Globals.pxToDP(w),
height: iOS ? Measure.pxToDP(h): Alloy.Globals.pxToDP(h)
});
image.addEventListener("click",goToEvent);
covers.add(image); //addView
//}
//},0);
}
var row = null;
if(args.rows) {
for(var j=0; j < args.rows.length; j++) {
//row = Alloy.createController("index/events/block/row",args.rows[j]).getView();
content.add(createRow(args.rows[j]));
}
}
container.add(createHeader(args));
container.add(covers);
container.add(content);
return container;
}
In particular, in the code provided, I call 4 times the function createRow() which creates a row inside the element (as seen in the picture). This function takes 7ms to 10ms to run for some reason. So calling it 4 times means it slows the whole process 28-40ms.
On Android the app doesn't lag at all. On iOS it stops user interaction completely until these operations are done
Using latest Titanium SDK (5.2.2GA) on Appcelerator Studio
Testing on iPhone 5, iOS simulator (4s,5,6,6s)
Thank you for your help