Create a SVG object with a arrow line Highcharts - svg

I need to draw a arrow line pointing to a particular point in my highcharts.
I was able to get the line drawn but not sure how to have a arrow before that being displayed.
Here is my fiddle and code.
http://jsfiddle.net/48kyq3wq/1/
$.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=usdeur.json&callback=?', function(data) {
var callout = function(chart) {
$('.cO').remove();
var xAxis = chart.xAxis[0],
yAxis = chart.yAxis[0],
point = chart.series[0].data[100],
lineXLength = 100,
lineYLength = -50;
chart.renderer.path(['M', xAxis.toPixels(point.x), yAxis.toPixels(point.y), 'L', xAxis.toPixels(point.x) + lineXLength, yAxis.toPixels(point.y) + lineYLength,]).attr({
'stroke-width': 5,
stroke: 'red',
zIndex: 0
}).addClass('C0').add();
chart.renderer.label('Custom label', xAxis.toPixels(point.x) + lineXLength, yAxis.toPixels(point.y) + lineYLength - 22, 'rect')
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
r: 5,
width: 100,
height: 30,
zIndex: 6
}).addClass('cO')
.add();
};
Highcharts.chart('container', {
chart: {
events: {
load: function() {
callout(this);
},
redraw: function() {
callout(this);
}
}
},
xAxis: {
type: 'datetime'
},
legend: {
enabled: false
},
series: [{
data: data
}]
});
});

You need to create an arrow and rotate it depending on the position of point and the label.
Create a path which connects the label and the point
const x0 = label.x;
const y0 = label.y + label.height / 2;
const x1 = x;
const y1 = y;
const path = chart.renderer.path([
'M', label.x, label.y + label.height / 2,
'L', x, y,
]).attr({
'stroke-width': 1,
stroke: 'black',
zIndex: 6
}).add()
Create a method for rotating points. It will rotate the point around the top point of the arrow
function rotatePoint(c, angle, p) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
const x = p[0] - c[0];
const y = p[1] - c[1];
const nx = x * cos - y * sin;
const ny = x * sin + y * cos;
return [nx + c[0], ny + c[1]];
}
Create an arrow, find the angle between the label and the point and rotate the arrow according to the angle.
const array = [[x1 - 5, y1 - 15], [x1 + 5, y1 - 15], [x1, y1], [x1 - 5, y1- 15]]
const angle = Math.atan2(y0 - y1, x0 - x1) + 90 * Math.PI / 180;
const ma = array.map(point => rotatePoint(array[2], angle, point));
Render the arrow
const arrow = chart.renderer.path({
fill: 'black',
zIndex: 6,
d: ['M', ma[0].join(' '), 'L', ma[1].join(' '), ma[2].join(' '), ma[3].join(' ')].join(' ')
}).add()
You should also consider cases when the point is on the right of the label and the top of the label - rotation and calculation of the angle stay the same but connecting the point with the label needs to be adjusted.
Live example and output
http://jsfiddle.net/savpscya/

Related

Openylayers 2 - Draw a circle or a point with radius

I have a position (lon, lat) and a radius. The radius is a float value and is in the km (kilometer) unit.
For example: Lat=46.92, Lon=7.95 and radius 5km.
Now my goal is to draw a circle or point at this position with this radius.
I'm using the follow transformation:
function GetLonLatObj(lat, lon) {
var lonLat = new OpenLayers.LonLat( lon ,lat )
.transform(
new OpenLayers.Projection("EPSG:4326"), // Transformation aus dem Koordinatensystem WGS 1984
map.getProjectionObject() // in das Koordinatensystem 'Spherical Mercator Projection'
);
return lonLat
}
To draw the circle on the right position is no problem. Unfortunately the circle is to small and has no radius of 5 km on the map.
Here is my code:
var map = new OpenLayers.Map("mapdiv");
map.addLayer(new OpenLayers.Layer.OSM('OpenStreetMap',
"https://a.tile.openstreetmap.org/${z}/${x}/${y}.png",
"https://b.tile.openstreetmap.org/${z}/${x}/${y}.png",
"https://c.tile.openstreetmap.org/${z}/${x}/${y}.png"
));
map.addLayer(vectorNotamLayer);
zoom = 12;
var vectorNotamLayer = new OpenLayers.Layer.Vector("Simple Geometry", {
styleMap: new OpenLayers.StyleMap({
'default': {
pointRadius: "${Radius}",
label : "${Title}",
fontSize: "20px",
fontWeight: "bold",
labelAlign: "cm",
labelYOffset: -10,
fillOpacity: 0.4,
labelOutlineWidth: 3
}
}),
});
var lon = 7.95;
var lat = 46.92;
var radius = 5;
var upper = item.upper;
var lower = item.lower;
var number = item.number;
var title = series + number + " " + lower + "-" + upper;
var latLonObj = GetLonLatObj(lat, lon);
var point = new OpenLayers.Geometry.Point(latLonObj.lon, latLonObj.lat);
var pointFeature = new OpenLayers.Feature.Vector(point);
pointFeature.attributes = {
Title: title,
Radius: radius,
};
vectorNotamLayer.addFeatures([pointFeature]);
Can sombody help me here please?
Edit:
I changed the code from:
var pointFeature = new OpenLayers.Feature.Vector(point);
pointFeature.attributes = {
Title: title,
Radius: radius,
};
To
var poly = OpenLayers.Geometry.Polygon.createRegularPolygon
(
point,
radius,
36,
0
);

fabricJS Not persisting Floodfill

I have an algorithm for Floodfilling a canvas. Im trying to incorporate this with fabricJS. So here is the dilemna.... I create a fabric.Canvas(). Which creates a wrapper canvas and also an upper-canvas canvas. I click on the canvas to apply my Floodfill(). This works fine and applies my color. But as soon as i go to drag my canvas objects around, or add additional objects to the canvas, the color disappears and looks like it resets of sort.
Any idea why this is?
This happen because fabricjs wipe out all canvas every frame and redraw from its internal data.
I made a JSfiddle that implements Flood Fill for Fabric JS. Check it here: https://jsfiddle.net/av01d/dfvp9j2u/
/*
* FloodFill for fabric.js
* #author Arjan Haverkamp (av01d)
* #date October 2018
*/
var FloodFill = {
// Compare subsection of array1's values to array2's values, with an optional tolerance
withinTolerance: function(array1, offset, array2, tolerance)
{
var length = array2.length,
start = offset + length;
tolerance = tolerance || 0;
// Iterate (in reverse) the items being compared in each array, checking their values are
// within tolerance of each other
while(start-- && length--) {
if(Math.abs(array1[start] - array2[length]) > tolerance) {
return false;
}
}
return true;
},
// The actual flood fill implementation
fill: function(imageData, getPointOffsetFn, point, color, target, tolerance, width, height)
{
var directions = [[1, 0], [0, 1], [0, -1], [-1, 0]],
coords = [],
points = [point],
seen = {},
key,
x,
y,
offset,
i,
x2,
y2,
minX = -1,
maxX = -1,
minY = -1,
maxY = -1;
// Keep going while we have points to walk
while (!!(point = points.pop())) {
x = point.x;
y = point.y;
offset = getPointOffsetFn(x, y);
// Move to next point if this pixel isn't within tolerance of the color being filled
if (!FloodFill.withinTolerance(imageData, offset, target, tolerance)) {
continue;
}
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
if (x < minX || minX == -1) { minX = x; }
if (y < minY || minY == -1) { minY = y; }
// Update the pixel to the fill color and add neighbours onto stack to traverse
// the fill area
i = directions.length;
while (i--) {
// Use the same loop for setting RGBA as for checking the neighbouring pixels
if (i < 4) {
imageData[offset + i] = color[i];
coords[offset+i] = color[i];
}
// Get the new coordinate by adjusting x and y based on current step
x2 = x + directions[i][0];
y2 = y + directions[i][1];
key = x2 + ',' + y2;
// If new coordinate is out of bounds, or we've already added it, then skip to
// trying the next neighbour without adding this one
if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || seen[key]) {
continue;
}
// Push neighbour onto points array to be processed, and tag as seen
points.push({ x: x2, y: y2 });
seen[key] = true;
}
}
return {
x: minX,
y: minY,
width: maxX-minX,
height: maxY-minY,
coords: coords
}
}
}; // End FloodFill
var fcanvas; // Fabric Canvas
var fillColor = '#f00';
var fillTolerance = 2;
function hexToRgb(hex, opacity) {
opacity = Math.round(opacity * 255) || 255;
hex = hex.replace('#', '');
var rgb = [], re = new RegExp('(.{' + hex.length/3 + '})', 'g');
hex.match(re).map(function(l) {
rgb.push(parseInt(hex.length % 2 ? l+l : l, 16));
});
return rgb.concat(opacity);
}
function floodFill(enable) {
if (!enable) {
fcanvas.off('mouse:down');
fcanvas.selection = true;
fcanvas.forEachObject(function(object){
object.selectable = true;
});
return;
}
fcanvas.deactivateAll().renderAll(); // Hide object handles!
fcanvas.selection = false;
fcanvas.forEachObject(function(object){
object.selectable = false;
});
fcanvas.on({
'mouse:down': function(e) {
var mouse = fcanvas.getPointer(e.e),
mouseX = Math.round(mouse.x), mouseY = Math.round(mouse.y),
canvas = fcanvas.lowerCanvasEl,
context = canvas.getContext('2d'),
parsedColor = hexToRgb(fillColor),
imageData = context.getImageData(0, 0, canvas.width, canvas.height),
getPointOffset = function(x,y) {
return 4 * (y * imageData.width + x)
},
targetOffset = getPointOffset(mouseX, mouseY),
target = imageData.data.slice(targetOffset, targetOffset + 4);
if (FloodFill.withinTolerance(target, 0, parsedColor, fillTolerance)) {
// Trying to fill something which is (essentially) the fill color
console.log('Ignore... same color')
return;
}
// Perform flood fill
var data = FloodFill.fill(
imageData.data,
getPointOffset,
{ x: mouseX, y: mouseY },
parsedColor,
target,
fillTolerance,
imageData.width,
imageData.height
);
if (0 == data.width || 0 == data.height) {
return;
}
var tmpCanvas = document.createElement('canvas'), tmpCtx = tmpCanvas.getContext('2d');
tmpCanvas.width = canvas.width;
tmpCanvas.height = canvas.height;
var palette = tmpCtx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height); // x, y, w, h
palette.data.set(new Uint8ClampedArray(data.coords)); // Assuming values 0..255, RGBA
tmpCtx.putImageData(palette, 0, 0); // Repost the data.
var imgData = tmpCtx.getImageData(data.x, data.y, data.width, data.height); // Get cropped image
tmpCanvas.width = data.width;
tmpCanvas.height = data.height;
tmpCtx.putImageData(imgData,0,0);
fcanvas.add(new fabric.Image(tmpCanvas, {
left: data.x,
top: data.y,
selectable: false
}))
}
});
}
$(function() {
// Init Fabric Canvas:
fcanvas = new fabric.Canvas('c', {
backgroundColor:'#fff',
enableRetinaScaling: false
});
// Add some demo-shapes:
fcanvas.add(new fabric.Circle({
radius: 80,
fill: false,
left: 100,
top: 100,
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Triangle({
width: 120,
height: 160,
left: 50,
top: 50,
stroke: '#000',
fill: '#00f',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 120,
height: 160,
left: 150,
top: 50,
fill: 'red',
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 200,
height: 120,
left: 200,
top: 120,
fill: 'green',
stroke: '#000',
strokeWidth: 2
}));
/* Images work very well too. Make sure they're CORS
enabled though! */
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function() {
fcanvas.add(new fabric.Image(img, {
left: 300,
top: 100,
angle: 30,
}));
}
img.src = 'http://misc.avoid.org/chip.png';
});

fabricjs how i can make an angle measurement?

i want to use fabricjs for create an Object and can measure some images sections .
Update:
I follow sample of http://fabricjs.com/stickman/ and seems I have something but need to improve more
here is what i have https://jsfiddle.net/mavirroco/gtfw58st/
(function () {
var canvas = this.__canvas = new fabric.Canvas('c', {
selection: false
});
var text1 = new fabric.Text('0 Deg', {
fontSize: 20,
fontFamily: 'Georgia',
top: 10,
left: 100
});
canvas.add(text1);
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
function makeCircle(left, top, line1, line2, line3, line4) {
var c = new fabric.Triangle({
left: left,
top: top,
strokeWidth: 5,
fill: '#fff',
stroke: '#666',
angle: -180,
width: 10,
height: 10
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
c.line3 = line3;
c.line4 = line4;
return c;
}
function makeLine(coords) {
return new fabric.Line(coords, {
fill: 'red',
stroke: 'red',
strokeWidth: 5,
selectable: false
});
}
var line2 = makeLine([250, 175, 250, 250]),
line3 = makeLine([250, 250, 300, 350]),
line4 = makeLine([250, 250, 200, 350]);
canvas.add(line3, line4);
canvas.add(
makeCircle(line2.get('x2'), line2.get('y2'), line2, line3, line4),
makeCircle(line3.get('x2'), line3.get('y2'), line3),
makeCircle(line4.get('x2'), line4.get('y2'), line4));
canvas.on('object:moving', function (e) {
var p = e.target;
p.line1 && p.line1.set({
'x2': p.left,
'y2': p.top
});
p.line2 && p.line2.set({
'x1': p.left,
'y1': p.top
});
p.line3 && p.line3.set({
'x1': p.left,
'y1': p.top
});
p.line4 && p.line4.set({
'x1': p.left,
'y1': p.top
});
canvas.renderAll();
dy = line3.get('y2') - line4.get('y2');
dx = line3.get('x2') - line4.get('x2');
theta = Math.atan2(dy, dx);
theta *= 180 / Math.PI // rads to degs
text1.setText(parseFloat(theta).toFixed(2));
});
})();
y11 = line3.get('y1');
y12 = line3.get('y2');
y21 = line4.get('y1');
y22 = line4.get('y2');
x11 = line3.get('x1');
x12 = line3.get('x2');
x21 = line4.get('x1');
x22 = line4.get('x2');
angle1 = Math.atan2(y11 - y12, x11 - x12);
angle2 = Math.atan2(y21 - y22, x21 - x22);
angle = angle1 - angle2;
angle = angle*180/Math.PI;
if(angle < 0) angle = -angle;
if(360 - angle < angle) angle = 360 - angle;
text1.setText(angle.toString());
https://jsfiddle.net/gtfw58st/5/

Problems when interactively resizing and moving svg rectangles with D3

I am trying to create panel components that will hold some visualizations.
I am making the panel component with svgs. They look ok, but I am getting some weird behavior when resizing and moving the panels.
var groups = ["uno", "dos", "tres", "cuatro"];
var w = 350;
var x = 0;
var y = 0;
var width = 800;
var h = 200;
var height = 800;
var val = [];
var drag = d3.behavior.drag()
.origin(Object)
.on("drag", move);
var resize = d3.behavior.drag()
.origin(Object)
.on("drag", dragResize);
svg = d3.select("body").append("div").append("svg");
charts = svg.selectAll("g.chart")
.data(groups); //(dims);
box = charts.enter()
.append("g").classed("chart", true)
.attr("id", function(d,i) { return "box"+i})
//.data([{x: 95, y: 0}]);
box.append("rect").classed("box", true)
var t = box.append("rect").classed("titleBox", true)
t.call(drag);
box.append("text").classed("title", true).data(groups)
box.append("text").classed("legend", true).data(groups)
box.append("rect").classed("icon", true)
.call(resize);
box.selectAll("rect.box")
.data([{x: 95, y: 0}])
.attr({
x: function(d) { return d.x; },
y: function(d) { return d.y; },
width: w,
height: function(d) { return 200}//d.length*30 + 60}
})
box.selectAll("rect.titleBox")
.classed("drag", true)
.data([{x: 95, y: 0}])
.attr({
x: function(d) { return d.x; },
y: function(d) { return d.y; },
width: w,
height: 25,
fill: "#000000"
})
box.selectAll("text.title")
.attr({
x: 105,
y: 20,
width: 350,
height: 25,
fill: "#ffffff"
})
.text(function(d) {
console.log("i from title "+ d);
return d;
})
box.selectAll("text.legend")
.attr({
x: 105,
y: 45,
width: 200,
height: 25,
fill: "#999999"
})
.text(function(d) {
return d;
})
box.selectAll("rect.icon")
.data([{x: 429, y: 184}])
.attr({
x: function(d) { return d.x; },
y: function(d) { return d.y; },
width: 16,
height: 16,
fill: "#999999"
})
var dx = 429;
var dy = 184;
function move(){
var dragTarget = d3.select(this);
var dragObject = d3.select(this.parentNode);
console.log("move x:"+x+" y:"+y);
//console.log("d3.event.x:"+d3.event.x+" d3.event.y:"+d3.event.y);
x += d3.event.x - parseInt(dragTarget.attr('x'));
y += d3.event.y - parseInt(dragTarget.attr("y"));
console.log("x:"+x+" y:"+y);
dragObject
.attr("transform", "translate(" + x + "," + y + ")")
};
function dragResize(){
var dragx = Math.max(dx + (16/2), Math.min(w, dx + width + d3.event.dx));
var dragy = Math.max(dy + (16/2), Math.min(h, dy + height + d3.event.dy));
//console.log("resize x:"+x+" y:"+y);
console.log("d3.event.x:"+d3.event.dx+" d3.event.y:"+d3.event.dy);
var dragTarget = d3.select(this);
var dragObject = d3.select(this.parentNode);
var o = dragObject.select("rect.box");
var o1 = dragObject.select("rect.titleBox");
var oldx = dx;
var oldy = dy;
dx = Math.max(0, Math.min(dx + width - (16 / 2), d3.event.x));
dy = Math.max(0, Math.min(dy + height - (16 ), d3.event.y));
w = w - (oldx - dx);
h = h - (oldy - dy);
dragTarget
.attr("x", function(d) { return dragx - (16/2) })
.attr("y", function(d) { return dragy - (16) })
o.attr("width", w)
.attr("height", h);
o1.attr("width", w);
};
I have posted the code at http://jsfiddle.net/dtqY5/
The problem is the following: I can move each panel, by dragging the title area, with no problem. Howvwer, after I resize any of the panels, I cannot move them anymore. They jump to their original position. The x and y becones NaN, but I cannot understand why.
ANy ideas and suggestions will be welcomed.
D3 uses the drag.origin accessor you provide to calculate an offset. Since the access you provide is just an empty object, this offset is NaN which results in x and y on the event also being NaN.
If you remove drag.origin altogether it uses the current mouse position as the origin which makes the panels jump when you start dragging. If you specify the origin to be the position of the shape being dragged it looks better:
.origin(function() {
var current = d3.select(this);
return {x: current.attr("x"), y: current.attr("y") };
})
Here's an updated fiddle: http://jsfiddle.net/4nvhc/

Adding an ID to a Raphael circle

I'm trying to make a drag curver with Raphael.
Please look for this:
r.circle(x, y, 6).attr({
fill: "#ff0000",
stroke: "none",
id: "cir1"
}),
This is my code:
$(document).ready(function() {
var r = Raphael("holder", 1583, 600),
discattr = {
fill: "#ff0000",
stroke: "none"
};
r.rect(9950, 9990, 6199, 4199, 9910, 777777777777).attr({
stroke: "#000000"
});
r.text(310, 20, "").attr({
fill: "#fff",
"font-size": 16
});
function curve(x, y, ax, ay, bx, by, zx, zy, color) {
var path = [["M", x, y], ["C", ax, ay, bx, by, zx, zy]],
path2 = [["M", x, y], ["L", ax, ay], ["M", bx, by], ["L", zx, zy]],
curve = r.path(path).attr({
stroke: color || Raphael.getColor(),
"stroke-width": 4,
"id": "path"
}),
controls = r.set(
r.path(path2).attr({
stroke: "#000000",
id: 'path'
}), r.circle(x, y, 6).attr({
fill: "#ff0000",
stroke: "none",
id: "cir1"
}), r.circle(ax, ay, 6).attr({
fill: '#ff0000',
stroke: 'none',
'id': 'cir2'
}), r.circle(bx, by, 6).attr({
fill: "#ff0000",
stroke: "none",
id: "cir3"
}), r.circle(zx, zy, 6).attr({
fill: "#ff0000",
stroke: "none",
id: "cir4"
}));
t = false;
controls[1].update = function(x, y) {
var X = this.attr("cx") + x,
Y = this.attr("cy") + y;
this.attr({
cx: X,
cy: Y
});
path[0][1] = X;
path[0][2] = Y;
path2[0][1] = X;
path2[0][2] = Y;
t = false;
controls[2].update(x, y);
};
controls[2].update = function(x, y) {
var X = this.attr("cx") + x,
Y = this.attr("cy") + y;
this.attr({
cx: X,
cy: Y
});
path[1][1] = X;
path[1][2] = Y;
path2[1][1] = X;
path2[1][2] = Y;
t = false;
curve.attr({
path: path
});
controls[0].attr({
path: path2
});
};
controls[3].update = function(x, y) {
var X = this.attr("cx") + x,
Y = this.attr("cy") + y;
this.attr({
cx: X,
cy: Y
});
path[1][3] = X;
path[1][4] = Y;
path2[2][1] = X;
path2[2][2] = Y;
t = false;
curve.attr({
path: path
});
controls[0].attr({
path: path2
});
};
controls[4].update = function(x, y) {
var X = this.attr("cx") + x,
Y = this.attr("cy") + y;
this.attr({
cx: X,
cy: Y
});
path[1][5] = X;
path[1][6] = Y;
t = false;
path2[3][1] = X;
path2[3][2] = Y;
controls[3].update(x, y);
};
controls.drag(move, up);
t = false;
}
function move(dx, dy) {
this.update(dx - (this.dx || 0), dy - (this.dy || 0));
this.dx = dx;
this.dy = dy;
t = false;
}
function up() {
this.dx = this.dy = 0;
t = false;
}
// curve(70, 100, 80, 100, 130, 154, 170, 200, "hsb(0, .75, .75)");
curve(100, 100, 100, 100, 100, 100, 100, 100, "#ff0000");
});
I am trying to add an id to each circle, but it is not working for some reason.
You can add miscellaneous data, like a custom ID, using the element.data() function. Your circle code snippet could be the following:
r.circle(x, y, 6).attr({
fill: "#ff0000",
stroke: "none"
}).data("id", "cir1"),

Resources