I use snap.svg, and try to achieve such bechavior. The svg should be scale and centered on mobile, like on the image. Also ist possible when hovering over dots - country names should appear & be clickable links. In any case is possible to optimize the code? And how to centered svg on mobile one? Codepen update
var s = Snap("#svg");
var startPoint, endPoint;
var path;
var circleProps = {
fill: 'white',
strokeWidth: 3,
opacity: 0,
class: "cicles"
};
circleProps.stroke = 'white';
startPoint = s.circle(770,415,5);
startPoint.attr(circleProps);
endPoint = s.circle(795,386,5);
endPoint.attr(circleProps);
endPoint1 = s.circle(820,445,5);
endPoint1.attr(circleProps);
endPoint2 = s.circle(900,500,5);
endPoint2.attr(circleProps);
endPoint3 = s.circle(1050,630,5);
endPoint3.attr(circleProps);
endPoint4 = s.circle(1170,540,5);
endPoint4.attr(circleProps);
endPoint5 = s.circle(810,600,5);
endPoint5.attr(circleProps);
endPoint6 = s.circle(750,460,5);
endPoint6.attr(circleProps);
text = s.text(735,350, ["LATVIA"]);
text.attr({
fill: "white",
"font-size": "20px",
"font-family": "Verdana",
"background": "black",
});
p1 = text.selectAll("tspan:nth-child(2)");
p3 = text.selectAll("tspan:nth-child(4)");
path = s.path("M770,415,Q790,360,795,386").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
path1 = s.path("M770,415,Q800,410,820,445").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
path2 = s.path("M770,415,Q880,410,900,500").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
path3 = s.path("M770,415,Q1060,410,1050,630").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
path4 = s.path("M770,415,Q1060,410,1170,540").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
path5 = s.path("M770,415,Q810,410,810,600").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
path6 = s.path("M770,415,Q810,410,750,460").attr({
fill: "none",
stroke: "white",
strokeWidth: 4
});
var pathLength = Snap.path.getTotalLength( path );
path.node.style.strokeDasharray = pathLength + ' ' + pathLength;
path.node.style.strokeDashoffset = pathLength;
var pathLength1 = Snap.path.getTotalLength( path1 );
path1.node.style.strokeDasharray = pathLength1 + ' ' + pathLength1;
path1.node.style.strokeDashoffset = pathLength1;
var pathLength2 = Snap.path.getTotalLength( path2 );
path2.node.style.strokeDasharray = pathLength2 + ' ' + pathLength2;
path2.node.style.strokeDashoffset = pathLength2;
var pathLength3 = Snap.path.getTotalLength( path3 );
path3.node.style.strokeDasharray = pathLength3 + ' ' + pathLength3;
path3.node.style.strokeDashoffset = pathLength3;
var pathLength4 = Snap.path.getTotalLength( path4 );
path4.node.style.strokeDasharray = pathLength4 + ' ' + pathLength4;
path4.node.style.strokeDashoffset = pathLength4;
var pathLength5 = Snap.path.getTotalLength( path5 );
path5.node.style.strokeDasharray = pathLength5 + ' ' + pathLength5;
path5.node.style.strokeDashoffset = pathLength5;
var pathLength6 = Snap.path.getTotalLength( path6 );
path6.node.style.strokeDasharray = pathLength6 + ' ' + pathLength6;
path6.node.style.strokeDashoffset = pathLength6;
p1.animate({
fill: "white",
}, 600, mina.easeout);
startPoint.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
setTimeout(function() {
endPoint.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
endPoint1.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
endPoint2.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
endPoint3.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
endPoint4.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
endPoint5.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
endPoint6.animate({
r: 5,
opacity: 1
}, 600, mina.easeout);
}, 1800);
setTimeout(function() {
Snap.animate(pathLength, 0, function( value ) {
path.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
setTimeout(function() {
Snap.animate(pathLength1, 0, function( value ) {
path1.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
setTimeout(function() {
Snap.animate(pathLength2, 0, function( value ) {
path2.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
setTimeout(function() {
Snap.animate(pathLength3, 0, function( value ) {
path3.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
setTimeout(function() {
Snap.animate(pathLength4, 0, function( value ) {
path4.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
setTimeout(function() {
Snap.animate(pathLength5, 0, function( value ) {
path5.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
setTimeout(function() {
Snap.animate(pathLength6, 0, function( value ) {
path6.node.style.strokeDashoffset = value;
}, 1600);
}, 2700);
// get cordiante
function svgPoint(element, x, y) {
var pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt.matrixTransform(element.getScreenCTM().inverse());
}
var svg = document.getElementById('svg');
var NS = svg.getAttribute('xmlns');
var local = svg.getElementById('Markets');
var coords = document.getElementById('coords');
svg.addEventListener('mousemove', function(e) {
var
x = e.clientX,
y = e.clientY,
svgP = svgPoint(svg, x, y),
svgL = svgPoint(local, x, y);
// output co-ordinates
coords.textContent =
'[page: ' + x + ',' + y +
'] => [svg space: ' + Math.round(svgP.x) + ',' + Math.round(svgP.y) +
'] [local transformed space: ' + Math.round(svgL.x) + ',' + Math.round(svgL.y) + ']'
;
}, false);
The solution here CODEPEN The Snap SVG is powerful library, I use it API. I'm not quite like my stuff with responsive solution.
I append the text tag to the link tag with such syntax, after that for each link set attributes with a link, and using the CSS opacity on hover it's possible to click the city name in SVG.
a.add(s.text(715,375, " LATVIA ").attr(linkStyle));
a.node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'https://uk.wikipedia.org/wiki/%D0%9B%D0%B0%D1%82%D0%B2%D1%96%D1%8F');
.map {
height:450px;
max-height: 450px;
position: relative;
left: calc(100vw/2 - 100vh/2);
}
#media screen and (min-width: 640px){
.map {
height: 553px;
max-height: 553px;
left: 0;
left: calc(80vw/2 - 100vh/2);
}
}
#media screen and (min-width: 1200px){
.map {
height: 838px;
max-height: 838px;
left: 0;
}
}
Related
I'm trying to check for a collision between 2 text objects and using the intersectsWithObject. It's working but it's taking the bouding rect into account. Is it possible to check on pixel level?
Current behaviour:
Wanted behaviour:
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const text = new fabric.Textbox('some text', {
width: 300,
fontSize: 70,
top: 120,
left: 100
});
const text2 = new fabric.Textbox('some more text', {
width: 350,
fontSize: 50,
top: 200,
left: 20,
})
if (text.intersectsWithObject(text2, true, true)) {
text.set('fill', 'red');
}
else {
text.set('fill', 'black');
}
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left + 0.5,
bound.top + 0.5,
bound.width,
bound.height
);
})
});
canvas.add(text);
canvas.add(text2);
https://jsbin.com/menadejato/edit?js,console,output
I have attached the funnel visualization code that I have so far.
$(function() {
var dataEx = [
['1 Visit', 352000],
['2 Visits', 88000],
['3+ Visits', 42000]
],
len = dataEx.length,
sum = 0,
minHeight = 0.05,
data = [];
//specify your percent of prior visit value manually here:
var perc = [100, 25, 48];
for (var i = 0; i < len; i++) {
sum += dataEx[i][1];
}
for (var i = 0; i < len; i++) {
var t = dataEx[i],
r = t[1] / sum;
data[i] = {
name: t[0],
y: (r > minHeight ? t[1] : sum * minHeight),
percent: perc[i], // <----- this here is manual input
//percent: Math.round(r * 100), <--- this here is mathematical
label: t[1]
}
}
console.log(dataEx, data)
$('#container').highcharts({
chart: {
type: 'funnel',
marginRight: 100,
events: {
load: function() {
var chart = this;
Highcharts.each(chart.series[0].data, function(p, i) {
var bBox = p.dataLabel.getBBox()
p.dataLabel.attr({
x: (chart.plotWidth - chart.plotLeft) / 2,
'text-anchor': 'middle',
y: p.labelPos.y - (bBox.height / 2)
})
})
},
redraw: function() {
var chart = this;
Highcharts.each(chart.series[0].data, function(p, i) {
p.dataLabel.attr({
x: (chart.plotWidth - chart.plotLeft) / 2,
'text-anchor': 'middle',
y: p.labelPos.y - (bBox.height / 2)
})
})
}
},
},
title: {
text: 'Guest Return Funnel',
x: -50
},
tooltip: {
//enabled: false
formatter: function() {
return '<b>' + this.key +
'</b><br/>Percent of Prior Visit: '+ this.point.percent + '%<br/>Guests: ' + Highcharts.numberFormat(this.point.label, 0);
}
},
plotOptions: {
series: {
allowPointSelect: true,
borderWidth: 12,
animation: {
duration: 400
},
dataLabels: {
enabled: true,
connectorWidth: 0,
distance: 0,
formatter: function() {
var point = this.point;
console.log(point);
return '<b>' + point.name + '</b> (' + Highcharts.numberFormat(point.label, 0) + ')<br/>' + point.percent + '%';
},
minSize: '10%',
color: 'black',
softConnector: true
},
neckWidth: '30%',
neckHeight: '0%',
width: '50%',
height: '110%'
//old options are as follows:
//neckWidth: '50%',
//neckHeight: '50%',
//-- Other available options
//height: '200'
// width: pixels or percent
}
},
legend: {
enabled: false
},
series: [{
name: 'Unique users',
data: data
}]
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/funnel.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<div id="container" style="width: 500px; height: 400px; margin: 0 auto"></div>
What I want to do is manually change the color of each category (piece) of the funnel (for example, maybe first category red, second category orange, third category yellow). I know that there are some ways to enter in data into a series in Highcharts such as:
[['CATEOGRY', 'VALUE],...['CATEGORY','VALUE']]
or you can do an array with names value and specify something like
color: "#00FF00" inside of it.
So maybe I can use the second form of writing data into a series cause you can specify color.
However, how would I be able to specify color of the pieces WHILE ALSO ensuring that the data processing algorithm to scale when there are small values works and the rest of the code works?
Also, is there any way to specify color given the current array of data that I have in my code? Being dataEx = [['CATEOGRY', 'VALUE],...['CATEGORY','VALUE']]
You can simply set the color as the third element in dataEx array and then set it as a point color:
var dataEx = [
['1 Visit', 352000, 'red'],
['2 Visits', 88000, 'orange'],
['3+ Visits', 42000, 'yellow']
],
len = dataEx.length,
sum = 0,
minHeight = 0.05,
data = [];
//specify your percent of prior visit value manually here:
var perc = [100, 25, 48];
for (var i = 0; i < len; i++) {
sum += dataEx[i][1];
}
for (var i = 0; i < len; i++) {
var t = dataEx[i],
r = t[1] / sum;
data[i] = {
name: t[0],
color: t[2],
y: (r > minHeight ? t[1] : sum * minHeight),
percent: perc[i], // <----- this here is manual input
//percent: Math.round(r * 100), <--- this here is mathematical
label: t[1]
}
}
Live demo: https://jsfiddle.net/BlackLabel/8be3c1sm/
API Reference: https://api.highcharts.com/highcharts/series.funnel.data.color
I'm using Fabric.js to create a group of two rectangles that are lying next to each other. I want the left one to change it's color when I'm moving my mouse over it. Therefore I check if the position of the mouse-cursor is within the the boundary of the rectangle or not.
This works fine until I scale the group...
I made a few tests and found out that the properties of the group members don't change. So while the rectangles become larger they still show their old sizes.
This is my code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
//Show the sizes
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth());
console.log("rect2 width: " + e.target.item(1).getWidth());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
//Check if cursor is within the boundary of rect2
if(point.posX >= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2
&& point.posX <= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2 + e.target.item(1).getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.item(1).getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also made a Fiddle: https://jsfiddle.net/werschon/0uduqfpe/3/
If I make the group larger, my 'mouse-detection' doesn't work anymore, because the properties like left, top or width are not updating.
What do I need to do, to update the properties? Or is there another way of detecting if the mouse-cursor is above the rectangle?
Thanks.
I found a solution (Thanks to John Morgan): I have to calculate the properties of the group members by myself. With getScaleX() and getScaleY() I can get the scale-factors of the group. Then I need to multiply the properties of the group members with the appropriate scale-factor. As result I get the new values of the group member. In my example I only needed getScaleX() to calculate the width of rect2 so it looks like this: e.target.item(1).getWidth() * e.target.getScaleX().
This is the new code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth() * e.target.getScaleX());
console.log("rect2 width: " + e.target.item(1).getWidth() * e.target.getScaleX());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
if(point.posX >= e.target.getLeft() + e.target.getWidth() - e.target.item(1).getWidth() * e.target.getScaleX()
&& point.posX <= e.target.getLeft() + e.target.getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
e.target.item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
farbicCanvas.on('mouse:out', function(e)
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also created a new Fiddle. So now my mouse-detection works even after scaling
I have a high chart on my web page which is a line chart. It has a functionality to zoom and I capture the zoom event using chart.events.selection. That all is working fine.
But I want to continuously capture the selection event (i.e. basically click and drag event) to show a tooltip in the beginning and end of the selection to show the time user has selected. Couldn't find in high charts documentation. Any help would be appreciated.
Here is my current code to capture selection event:
$(obj.id).highcharts({
chart: {
type: 'areaspline',
backgroundColor:"rgba(0,0,0,0)",
zoomType:"x",
events: {
selection: function(event){
if(!event.xAxis)
return;
.....
Updated:
Updated example in which labels following selection marker: http://jsfiddle.net/pq0wn0xx/2/
I do not think there is a drag event (not for points), but you can wrap drag pointer's method.
Highcharts.wrap(Highcharts.Pointer.prototype, 'drag', function (p, e) {
p.call(this, e);
var H = Highcharts,
chart = this.chart,
selectionMarker = this.selectionMarker,
bBox,
xAxis,
labelLeft,
labelRight,
labelY,
attr,
css,
timerLeft,
timerRight;
if (selectionMarker) {
if (!chart.customLabels) {
chart.customLabels = [];
}
bBox = selectionMarker.getBBox();
xAxis = chart.xAxis[0];
labelLeft = chart.customLabels[0];
labelRight = chart.customLabels[1];
labelY = chart.plotTop + 10;
if (!labelLeft || !labelRight) {
attr = {
fill: Highcharts.getOptions().colors[0],
padding: 10,
r: 5,
zIndex: 8
};
css = {
color: '#FFFFFF'
};
labelLeft = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
labelRight = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
chart.customLabels.push(labelLeft, labelRight);
}
clearTimeout(timerLeft);
clearTimeout(timerRight);
labelLeft.attr({
x: bBox.x - labelLeft.getBBox().width,
y: labelY,
text: 'min: ' + H.numberFormat(xAxis.toValue(bBox.x), 2),
opacity: 1
});
labelRight.attr({
x: bBox.x + bBox.width,
y: labelY,
text: 'max: ' + H.numberFormat(xAxis.toValue(bBox.x + bBox.width), 2),
opacity: 1
});
timerLeft = setTimeout(function () {
labelLeft.fadeOut();
}, 3000);
timerRight = setTimeout(function () {
labelRight.fadeOut();
}, 3000);
}
});
Old answer:
The example from the official API
can be extended to what you need.
The code and the example on jsfiddle are below:
function positionLabels(e, chart) {
if (!chart.customLabels) {
chart.customLabels = [];
}
var labelLeft,
labelRight,
attr,
css,
xAxis,
xMin,
xMax,
yAxis,
yMin,
yMax,
yMiddle,
timerLeft,
timerRight;
if (!e.resetSelection) {
labelLeft = chart.customLabels[0];
labelRight = chart.customLabels[1];
if (!labelLeft || !labelRight) {
attr = {
fill: Highcharts.getOptions().colors[0],
padding: 10,
r: 5,
zIndex: 8
};
css = {
color: '#FFFFFF'
};
labelLeft = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
labelRight = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
chart.customLabels.push(labelLeft, labelRight);
}
clearTimeout(timerLeft);
clearTimeout(timerRight);
xAxis = e.xAxis[0].axis;
xMin = e.xAxis[0].min;
xMax = e.xAxis[0].max;
yAxis = chart.yAxis[0];
yMin = yAxis.min;
yMax = yAxis.max;
yMiddle = (yMax - yMin) * 0.95;
labelLeft.attr({
x: xAxis.toPixels(xMin) - labelLeft.getBBox().width,
y: yAxis.toPixels(yMiddle),
text: 'min: ' + Highcharts.numberFormat(xMin, 2),
opacity: 1
});
labelRight.attr({
x: xAxis.toPixels(xMax),
y: yAxis.toPixels(yMiddle),
text: 'max: ' + Highcharts.numberFormat(xMax, 2),
opacity: 1
});
timerLeft = setTimeout(function () {
labelLeft.fadeOut();
}, 2000);
timerRight = setTimeout(function () {
labelRight.fadeOut();
}, 2000);
}
}
example: http://jsfiddle.net/pq0wn0xx/
How do we always connect two objects with a line in Snap.svg?
Plunkr: https://plnkr.co/edit/IgY0jyHbWTLuH4FfZt6o?p=preview
var circleOne = s.circle(150, 150, 100);
var circleTwo = s.circle(450, 450, 100);
var boxOne = circleOne.getBBox();
var boxTwo = circleTwo.getBBox();
var line = s.line(boxOne.cx, boxOne.cy, boxTwo.cx, boxTwo.cy);
line.attr({
stroke: 'red'
});
var t = new Snap.Matrix();
t.translate(200, 0, boxTwo.cx, boxTwo.cy);
setTimeout(function() {
circleTwo.animate({ transform: t}, 500, mina.easein);
}, 1000);
Nevermind, figured it out!
setTimeout(function() {
circleTwo.animate({ transform: t}, 500, mina.easein);
line.animate({
x2: boxTwo.cx + 200
}, 500, mina.easein);
}, 1000);
https://plnkr.co/edit/IgY0jyHbWTLuH4FfZt6o?p=preview