I want to calculate the distance of a point from a line defined by 2 points.
I am using javascript and thats what I came up with using wikipedia: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
function distance(point1, point2, x0, y0) {
return ((Math.abs((point2.y - point1.y) * x0 -
(point2.x - point1.x) * y0 +
point2.x * point1.y -
point2.y * point1.x)) /
(Math.pow((Math.pow(point2.y - point1.y, 2) +
Math.pow(point2.x - point1.x, 2)),
0.5)));
}
The problem is that It doesn't seem accurate since if I enter these parameters :
alert(distance({ x: 1, y: 1 }, { x: 2, y: 2 }, 1, 0));
It returns 1/sqrt(2) instead of returning 1 (which is the distance between the point (1, 0) and the line at point (1, 1)
EDIT : I understand the code above does not do what I wanted it to do. It caulcated from a point to a line represented by 2 point but the line is INFINITE (I wanted something more like a vector which has 2 end-point)
I found the answer here
I think 1/sqrt(2) = 0.7071... is quite right. See the image:
Edit:
var board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-1, 3, 3, 0],
keepaspectratio: true,
axis: true
});
var f1 = function(x) {
return x;
};
board.create('functiongraph', [f1]);
board.create('point', [1, 1], {
size: 4,
name: '1,1'
});
board.create('point', [2, 2], {
size: 4,
name: '2,2'
});
var p1 = board.create('point', [1, 0], {
size: 4,
name: '1,0'
});
var p2 = board.create('point', [0.5, 0.5], {
size: 0,
name: '1,0'
});
var li2 = board.create('line', [p1, p2], {
straightFirst: false,
straightLast: false,
strokeWidth: 2,
dash: 2
});
<html>
<head>
<link rel="stylesheet" type="text/css" href="http://jsxgraph.uni-bayreuth.de/distrib/jsxgraph.css" />
<script type="text/javascript" src="http://jsxgraph.uni-bayreuth.de/distrib/jsxgraphcore.js"></script>
</head>
<body>
<div id="jxgbox" class="jxgbox" style="width:500px; height:600px;"></div>
</body>
</html>
To add a mathematical point of view, here follows a mathematical "proof" of why your function is correct:
The shortest distance between a point and a line is the distance on the perpendicular line to the point. In this case we know that the angle between the line and the x-axis is 45 degrees and we know that the distance from origo to (1, 0) equals 1.
Using:
... we see that your result is correct, since sin(angle) = the opposite side of a right-angled triangle / hypotenuse.
Related
I am interested in creating an interactive plot in plotly.js, where a user can click on the plot and add a new point at that position. Essentially this means retrieving the plot coordinates of a mouse click. Most of the built-in click events deal with capturing a click on an already plotted marker, rather than an arbitrary position within the plot.
The following question deals with this issue, Plotly.js create a point on click. However, the code appears to have been made obsolete by changes to plotly.js at some point. An example of the answer to that question was demo'd on codepen. It appears that the xy mouse position detection now only updates within the toolbar region. My guess is that there was a renaming in the various components that make up the plot.
Link to non-functioning code from the comments of that answers:
https://codepen.io/circleoncircles/pen/abObLLE
var traces = [{
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
mode: 'markers',
type: 'scatter'
}];
traces.push({
x: [2, 3, 4, 5],
y: [16, 5, 11, 9],
mode: 'markers',
type: 'scatter'
});
traces.push({
x: [1, 2, 3, 4],
y: [12, 9, 15, 12],
mode: 'markers',
type: 'scatter'
});
traces.push({
x: [],
y: [],
mode: 'markers',
type: 'scatter'
});
var myPlot = document.getElementById('myPlot')
Plotly.newPlot('myPlot', traces, {hovermode: 'closest'});
Number.prototype.between = function(min, max) {
return this >= min && this <= max;
};
Plotly.d3.select(".plotly").on('click', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
Plotly.extendTraces(myPlot, {
x: [
[x]
],
y: [
[y]
]
}, [3]);
}
});
Plotly.d3.select(".plotly").on('mousemove', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
console.log("Location X:"+x+" Y"+y)
document.getElementById("xvalue").value = x;
document.getElementById("yvalue").value = y;
}
});
Does anybody have an updated method for this?
I think I've got it. I would still consider this relatively hacky, so if someone else has a better way to handle it, I'm all ears and would be happy to assign a better answer.
All you really need to do is get the bounding box of the element that represents the plot field. It appears that one such element is that with the class gridlayer. Then I grabbed its bounding rect.
So the computation lines in the above would become:
var bgrect = document.getElementsByClassName('gridlayer')[0].getBoundingClientRect();
var x = ((e.x - bgrect['x']) / (bgrect['width'])) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.y - bgrect['y']) / (bgrect['height'])) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1];
One additional change is necessary if you want to use the latest version of plotly. d3 is no longer embedded in plotly, so it's necessary to get d3 from CDN (the most recent version I found to work for this is v5).
<script src="https://cdn.plot.ly/plotly-2.11.0.min.js"></script>
<script src="//d3js.org/d3.v5.min.js"></script>
In the script you just reference d3 at the root then:
d3.select(".plotly").on('click', function(d, i) {
var e = d3.event;
I revised the codepen example that was posted previously to this new method https://codepen.io/jranalli/pen/eYyVVgr
In a related question I was looking for a way to draw points in 3D space so that the points will move according to slider values. Now that is working, but the conic section (a parabola in this case) I am trying to draw through these points, is not drawn.
I thought that the constructor for the element "conic" might be picky about how the given points are defined, so I ended up adding as attributes "sub-objects" that are points that can be referred to when drawing the conic.
In my code below, the constructor function PPoint creates objects that have their respective attributes pcoord, which is a geometric object of type "point" created using the native jsxgraph constructor for points. pcoord is assigned when the method "draw" is called to the draw the points I_1-I-4 and p_1.
In the last lines of the code, the parabola should be drawn by referring to the pcoords of objects I_1-I_4 and p_1, but for some reason the parabola is not drawn.
How could this be fixed? Link to jsfiddle. The code is executed without error notifications when debugging.
HTML
<div id="jxgbox" class="jxgbox" style="width:500px; height:500px">
</div>
JS
const board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-10, 10, 10, -10],
axis: true,
showCopyright: true,
showNavigation: true,
pan: false,
grid: false,
zoom: {
factorX: 1.25,
factorY: 1.25,
wheel: false
}
});
//create z axis
var zAxis = board.create('axis', [
[0, 0],
[-1, -1]
], {
ticks: {
majorHeight: 10,
drawLabels: false
}
});
//create direction of view for projections
var cam = [4, 4, 30]; // [x,y,z]
var r = 6.0;
var origin = [0, 0, 0];
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//create slider for rotating the parabola
var sRadius = board.create('slider', [
[1, -8.5],
[6, -8.5],
[-10, 0, 10]
], {
name: 'angle',
needsRegularUpdate: true
//snapWidth: 1
});
//create slider for adjusting the angular speed
var sOmega = board.create('slider', [
[1, -7.5],
[6, -7.5],
[0, 0, 10]
], {
name: 'Omega',
needsRegularUpdate: true
//snapWidth: 1,
});
//fix parameters
const g = 9.81 //gravitational acceleration
const h0 = 5 //initial height of the water surface
//define radius from the y-axis for I3 and I4
const R34 = Math.sqrt(2);
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//function creates points for drawing conic sections
function PPoint(radius, sign, namep, fixval) {
this.R = radius;
this.S = sign;
this.Namep = namep;
this.Fixval = fixval;
this.pcoord = undefined; //Cartesian coordinates of the point, stored as a point
}
//method for drawing each Point
PPoint.prototype.draw = function(pp) {
board.create('point', [function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
KK = 1 / 4 * sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
c = [pp.S * pp.R * Math.sin(v), K1 / 2 * pp.R * pp.R - KK + h0, pp.S * pp.R * Math.cos(v)];
//store the dynamically assigned coordinates of the point for drawing the parabola
pp.pcoord = board.create('point', [function() {
return project(c, cam);
}], {
visible: false
}); //end storing pp.coord
return project(c, cam);
}], {
fixed: this.Fixval,
name: this.Namep,
visible: true
})
}
//create and draw points
var p_1 = new PPoint(0, -1, 'p_1', 'false');
var I_1 = new PPoint(r, 1, 'I_1', 'false');
var I_2 = new PPoint(r, -1, 'I_2', 'false');
var I_3 = new PPoint(R34, 1, 'I_3', 'false');
var I_4 = new PPoint(R34, -1, 'I_4', 'false');
p_1.draw(p_1)
I_1.draw(I_1)
I_2.draw(I_2)
I_3.draw(I_3)
I_4.draw(I_4)
//draw the rotating parabola
var prbl = board.create('conic', [I_1.pcoord, I_2.pcoord, I_3.pcoord, I_4.pcoord, p_1.pcoord], {
strokeColor: '#CA7291',
strokeWidth: 2,
trace :true
});
//debugger
There are two issues with this code:
1) In PPoint.draw the reference two the JSXGraph point does not work: in each update an new JSXGraph point is created. This makes the code slow and - moreover - does not influence the initial points supplied to the conic section. I would propose to change draw to this:
PPoint.prototype.draw = function(pp) {
pp.pcoord = board.create('point', [function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
KK = 1 / 4 * sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
c = [pp.S * pp.R * Math.sin(v),
K1 / 2 * pp.R * pp.R - KK + h0,
pp.S * pp.R * Math.cos(v)];
return project(c, cam);
}], {
fixed: this.Fixval,
name: this.Namep,
visible: true});
2) The second problem is that JSXGraph fails to plot degenerated conics through five points and suffers in precision if the conic is close to be degenerated (there are numerical issues with general parabolas). This is the case here for the start value omega = 0.
Here is a working example: https://jsfiddle.net/L2d4zt8q/
I would like to fill until a specific Y value in PlotlyJS. This is as far as I got from the PlotlyJS docs: Fiddle
{
"x": [
"2016-01-31T00:03:57.000Z",
"2016-02-12T04:35:26.000Z"
],
"y": [
100,
100
],
"fill": "tonexty",
"fillcolor": "#8adcb3"
}
In the documentation, there seems to be two options:
tonexty - Fills as below. The problem is 'tonexty' is a bit limiting - The use case is 'filling until the line', so shading ONLY above 110. Example:
tozeroy - Fills till zero:
Also, do you need to introduce a new trace in order to create a fill?
This means that if I have a chart as follows (with only one trace but a threshold line as a shape): I need to introduce another trace just to create a fill. Maybe there's something I missed in the docs, or this is the wrong approach altogether.
So, how do you fill an area in a trace above a specific Y value in PlotlyJS?
A solution is to use multiple traces.
Split all your traces between ones which are above 0 and ones which are not.
When you are done you can fill them (or not) with the 'tozeroy' value.
The following jsfiddle shows a working example.
The code is as following :
HTML:
<div id="myDiv" style="width:600px;height:250px;"></div>
JS:
var data = [
{
x: ['A', 'B', 'C', 'D'],
y: [1, 3, 6, 0],
fill: 'tozeroy',
fillcolor: '#8adcb3'
},
{
x: ['D', 'F', 'G', 'I'],
y: [0, -3, -2, 0],
fill: 'toself'
},
{
x: ['I', 'J', 'K'],
y: [0, 5, 7],
fill: 'tozeroy',
fillcolor: '#0adcb3'
}
];
Plotly.newPlot('myDiv', data);
The result looks as following :
Here is another solution exploiting Plotly's fill: "toself". The idea is to create a closed line trace which encloses the area above the threshold and the markers of the main line. Works for threshold values above zero and for numerical x-values.
The helper traces have their legend hidden and are grouped with the main trace, thereby preventing ugly artifacts when toggling the legend.
The function checks for each x-y-pair if the y-value is above the threshold, if yes
check if there is already a segment above the threshold and use this one OR create a new sgement
the segement starts from the y-value of the threshold and the intermediate x-value from the point above the threshold and the one before.
each segment is terminated with an y-value which is equal to the threshol and the x-value which the mean of the last point in the segment and the next one
The function itself can be surely written in a nicer way but it's just a proof-of-concept.
function dataToTraces(data, threshold) {
var fillers = [];
var emptyFiller = {
x: [],
y: [],
fill: "toself",
mode: "lines",
line: {
width: 0
},
opacity: 0.5,
fillcolor: "#8adcb3",
showlegend: false,
legendgroup: "main"
}
fillers.push(emptyFiller);
for (var i = 0; i < data.y.length; i += 1) {
if (data.y[i] >= threshold) {
if (i !== 0 && data.y[i - 1] < threshold) {
fillers[fillers.length - 1].x.push(data.x[i - 1] + (threshold - data.y[i - 1]) / (data.y[i] - data.y[i - 1]));
fillers[fillers.length - 1].y.push(threshold);
}
fillers[fillers.length - 1].x.push(data.x[i]);
fillers[fillers.length - 1].y.push(data.y[i]);
} else if (fillers[fillers.length - 1].x.length > 0) {
if (i !== 0 && data.y[i - 1] !== threshold) {
fillers[fillers.length - 1].x.push(data.x[i - 1] + (threshold - data.y[i - 1]) / (data.y[i] - data.y[i - 1]));
fillers[fillers.length - 1].y.push(threshold);
}
fillers.push(emptyFiller);
}
}
return fillers;
}
var data = [{
x: [0, 1, 2, 3, 4, 5, 6, 7, 8],
y: [1, 3, 6, 2, -1, 5, 1, 3, 0],
name: "main",
legendgroup: "main"
}];
var fillers = dataToTraces(data[0], 2);
Plotly.newPlot("myDiv", data.concat(fillers));
<div id="myDiv"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
Here's some simple code with two charts: JSFiddle
The ticks attribute works fine with the line chart but does not with the bar chart. Interestingly it works fine when posted on Chartist website (in examples section). But I really need to have it working in my .js file.
new Chartist.Bar(
'#barChart',
{
labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5'],
series: [
{name: 'one', data: [5, 6, 8, 9, 5]},
{name: 'two', data: [4, 5, 6, 5, 4]},
{name: 'three', data: [2, 4, 5, 2, 1]}
]
},
{
width: 565,
height: 200,
axisY: {
type: Chartist.FixedScaleAxis,
low: 0,
high: 10,
ticks: [0, 5, 10] // the ticks don't show up
}
}
);
I was looking for the diference between your code and examples code in chartist web. And they are loading scripts/all.js js url as :
<script async="" src="scripts/all.js"></script>
So, add that code and it will work :
<script src="//gionkunz.github.io/chartist-js/scripts/all.js"></script>
It seems to be a different module (AUTO-SCALE-AXIS.JS) from the main.js
https://jsfiddle.net/g4sqzseo/4/
That is the function from github project reference chartist.js which is not working for you :
/**
* The fixed scale axis uses standard linear projection of values along an axis. It makes use of a divisor option to divide the range provided from the minimum and maximum value or the options high and low that will override the computed minimum and maximum.
* **Options**
* The following options are used by this axis in addition to the default axis options outlined in the axis configuration of the chart default settings.
* ```javascript
* var options = {
* // If high is specified then the axis will display values explicitly up to this value and the computed maximum from the data is ignored
* high: 100,
* // If low is specified then the axis will display values explicitly down to this value and the computed minimum from the data is ignored
* low: 0,
* // If specified then the value range determined from minimum to maximum (or low and high) will be divided by this number and ticks will be generated at those division points. The default divisor is 1.
* divisor: 4,
* // If ticks is explicitly set, then the axis will not compute the ticks with the divisor, but directly use the data in ticks to determine at what points on the axis a tick need to be generated.
* ticks: [1, 10, 20, 30]
* };
* ```
*
* #module Chartist.FixedScaleAxis
*/
/* global Chartist */
(function (window, document, Chartist) {
'use strict';
function FixedScaleAxis(axisUnit, data, chartRect, options) {
var highLow = Chartist.getHighLow(data.normalized, options, axisUnit.pos);
this.divisor = options.divisor || 1;
this.ticks = options.ticks || Chartist.times(this.divisor).map(function(value, index) {
return highLow.low + (highLow.high - highLow.low) / this.divisor * index;
}.bind(this));
this.range = {
min: highLow.low,
max: highLow.high
};
Chartist.FixedScaleAxis.super.constructor.call(this,
axisUnit,
chartRect,
this.ticks,
options);
this.stepLength = this.axisLength / this.divisor;
}
function projectValue(value) {
return this.axisLength * (+Chartist.getMultiValue(value, this.units.pos) - this.range.min) / (this.range.max - this.range.min);
}
Chartist.FixedScaleAxis = Chartist.Axis.extend({
constructor: FixedScaleAxis,
projectValue: projectValue
});
}(window, document, Chartist));
Here you can see a chart created using graphael. http://jsfiddle.net/aNJxf/4/
It is shown with it's y axis correctly.
The first y value is 0.03100 and the maximum value at y axis is at 0.031
If we change the value to 0.03104 the maximum value at y axis becomes 1.03 and now all our points are in the bottom.
If we add another 0.00001, which makes that value 0.03105, the maximum at the axis y becomes 0.531 and now our points are shown at the wrong position of the chart.
It seems that something is going wrong while graphael calculates the maximum y axis value.
Why this happens? And how we can fix that?
The code that I have pasted there is
var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var x = [], y = [], y2 = [], y3 = [];
for (var i = 0; i < 1e6; i++) {
x[i] = i * 10;
y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];
var xVals =[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58];
var yVals = [0.03100,0.02259,0.02623,0.01967,0.01967,0.00788,0.02217,0.0137,0.01237,0.01764,0.0131,0.00942,0.0076,0.01463,0.02882,0.02093,0.02502,0.01961,0.01551,0.02227,0.0164,0.0191,0.00774,0.03076,0.0281,0.01338,0.02763,0.02334,0.00557,0.00023,0.01523,0.0263,0.03077,0.02404,0.02492,0.01954,0.01954,0.02337,0.01715,0.02271,0.00815,0.01343,0.00985,0.01837,0.00749,0.02967,0.01156,0.0083,0.00209,0.01538,0.01348,0.01353];
//r.text(160, 10, "Symbols, axis and hover effect").attr(txtattr);
var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines.symbols.attr({ r: 3 });
Thanks
Sorry, I'm not real familiar with gRaphael, but I did find that converting your yVals into whole numbers (by multiplying each by 1e5) seemed to rid your chart of the awkward behavior.
This suggests that it could be related to the algorithm gRaphael uses to find the max axis value (as you ask in your related question) when your values are small decimal values (and alter at even more significant digits).
I know there are inherent issues with float precision, but I can't be sure that applies to your case, or that your values are low enough to consider this.
Not the best workaround, but if it would be feasible for you, you could display the yValues in an order of magnitude larger, and remind the viewer that they are actually smaller than presented. For example, your chart could go from 0 to 3100 and remind your viewer that the scale is scale * 1e-5.