I want to copy an object while changing only a single property. Without Flow, I could do this using the object spread operator like this:
class Point { x: number = 10; y: number = 10; }
const p1 = new Point();
const p2 = {...p1, y: 5};
But when I add type annotations to p1 and p2 like this:
const p1 = new Point();
const p2 = {...p1, y: 5};
I get the following error:
11: const p2:Point = {...p1, y: 5};
^^^^^^^^^^^^^ object literal. This type is incompatible with
11: const p2:Point = {...p1, y: 5};
^^^^^ Point
How would I achieve this type of operation in a type safe way in Flow?
As an example, in Elm, I can do this:
p2 = { p1 | y = 5 }
There must be some equivalent in Flow.
When you use object spread, you don't get an exact copy of an object. Instead, you get a plain object with all source object's properties copied. So, Flow is right here, p2 is not Point. Try this instead:
type Point = { x: number, y: number };
const p1: Point = { x: 10, y: 10 };
const p2: Point = { ...p1, y: 5 };
Explanation: class does not work because it uses nominal typing but type works because that uses structural typing.
If you (really) need a class instead of a type alias you can simulate the Elm syntax p2 = { p1 | y = 5 } by defining a constructor with only one argument
export class Point {
x: number = 10;
y: number = 10;
constructor(fields?: { x: number, y: number }) {
Object.assign(this, fields)
}
}
const p1 = new Point()
const p2: Point = new Point({...p1, y: 5})
Related
I have seen scripts that claim to enter coordinates and it'll tell you if they intersect, but I have an array of X,Y values for a couple of "lines" but how do I cycle through the points to find out if they intersect?
I've included a photo of my graph and as you see, eventually my plots cross over, I just want to know if my values ever cross over (intersect).
How do I run through this to find out if any intersection ever occurs?
var Test = {
x: [8043, 10695, 13292, 17163, 20716, 25270],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test'
};
var Test2 = {
x: [8043, 10063, 12491, 16081, 19408, 23763],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test2'
};
var Test3 = {
x: [4700, 5943, 7143, 8841, 10366, 13452],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test3'
};
var data = [Test, Test2, Test3];
var layout = {
width: 700,
height: 700,
xaxis: {
type: 'log',
range: [3,5]
},
yaxis: {
type: 'log',
range: [-2,3]
}
};
Plotly.newPlot('myDiv', data,layout);
Path intercepts
This answer is a follow on from my answer to your most resent question.
The code snippet below will find the intercepts of the paths in the tables as structured in this questions example data using a modified intercept function from the answer link in may comment from aforementioned answer.
Note I am assuming that each table eg Test in your example data represents a curve (Path as a set of line segments) and that intercepts are not expected within a table but rather between tables.
Basic solution
It does this by checking each line segment in one table against each line segment in the other and storing all intercepts in an array.
Note that if a intercept is found at the start or end point of a line it may appear in the array of intercepts twice as the intercept test includes these points.
Note lines that are parallel, even if they have matching start and or end points will not count as intercepts.
The example is run against the example data and has a verbose console output to guide, if needed, you working through what ever data sets you are wrangling. The console logs can be removed without ill effect.
var Test = {
x: [8043, 10695, 13292, 17163, 20716, 25270],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test'
};
var Test2 = {
x: [8043, 10063, 12491, 16081, 19408, 23763],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test2'
};
var Test3 = {
x: [4700, 5943, 7143, 8841, 10366, 13452],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test3'
};
// Copy from here to end comment and place into you page (code base)
// lines outputting to the console eg console.log are just there to help you out
// and can be removed
const lineIntercepts = (() => {
const Point = (x, y) => ({x, y});
const Line = (p1, p2) => ({p1, p2});
const Vector = line => Point(line.p2.x - line.p1.x, line.p2.y - line.p1.y);
function interceptSegs(line1, line2) {
const a = Vector(line1), b = Vector(line2);
const c = a.x * b.y - a.y * b.x;
if (c) {
const e = Point(line1.p1.x - line2.p1.x, line1.p1.y - line2.p1.y);
const u = (a.x * e.y - a.y * e.x) / c;
if (u >= 0 && u <= 1) {
const u = (b.x * e.y - b.y * e.x) / c;
if (u >= 0 && u <= 1) {
return Point(line1.p1.x + a.x * u, line1.p1.y + a.y * u);
}
}
}
}
const PointFromTable = (t, idx) => Point(t.x[idx], t.y[idx]);
const LineFromTable = (t, idx) => Line(PointFromTable(t, idx++), PointFromTable(t, idx));
return function (table1, table2) {
const results = [];
var i = 0, j;
while (i < table1.x.length - 1) {
const line1 = LineFromTable(table1, i);
j = 0;
while (j < table2.x.length - 1) {
const line2 = LineFromTable(table2, j);
const point = interceptSegs(line1, line2);
if (point) {
results.push({
description: `'${table1.name}' line seg index ${i}-${i+1} intercepts '${table2.name}' line seg index ${j} - ${j+1}`,
// The description (line above) can be replaced
// with relevant data as follows
/* remove this line to include additional info per intercept
tableName1: table1.name,
tableName2: table2.name,
table_1_PointStartIdx: i,
table_1_PointEndIdx: i + 1,
table_2_PointStartIdx: j,
table_2_PointEndIdx: j + 1,
and remove this line */
x: point.x,
y: point.y,
});
}
j ++;
}
i++;
}
if (results.length) {
console.log("Found " + results.length + " intercepts for '" + table1.name + "' and '" + table2.name + "'");
console.log(results);
return results;
}
console.log("No intercepts found for '" + table1.name + "' and '" + table2.name + "'");
}
})();
// end of code
// Test and example code only from here down.
var res1 = lineIntercepts(Test, Test2);
var res2 = lineIntercepts(Test, Test3);
var res3 = lineIntercepts(Test2, Test3);
Using the above function
This bit of code illustrates how you extract intercepts from the function results
// find all the intercepts for the paths in tabels Test and Test2
const results = lineIntercepts(Test, Test2); // pass two tables
// If results not undefined then intercepts have been found
if (results) { // results is an array of found intercepts
// to get the point/s as there could be several
for (const intercept of results) { // loop over every intercept
// a single intercept coordinate
const x = intercept.x; // get x
const y = intercept.y; // get y
}
}
Better solutions
The paths look very much like they are a plot of some function thus there are even simpler solutions.
Rather than list out lines of code, I will direct you towards graphing calculators in case you are unaware of such useful time savers. They would have solved your problem in the time it takes to enter the data (by copy&paste thats not very long)
Online graphing calculators example apps Geogebra and Desmos and many more.
If I have two objects of partially matching shapes like
const point2d = { x: 0, y: 0 };
const point3d = { x: 0, y: 0, z: 0 };
then the valid type declaration in Flow would be
type Point2D = { x: number, y: number };
type Point3D = Point2D & { z: number };
At first, I tried to use the object spread operator and hit a problem quite soon because notation like
type Point3D = { ...Point2D, z: number };
is passed as valid but does not achieve the goal because in the end both x and y properties are missing from the Point3D type.
For example, I can do this with spread notation (which is wrong):
type Point2D = { x: number, y: number };
type Point3D = { ...Point2D, z: number };
const point2d: Point2D = { x: 0, y: 0 };
const point3d: Point3D = { y: 0, z: 0 }; // No errors
but cannot miss the x property in object declaration with type intersection notation:
type Point2D = { x: number, y: number };
type Point3D = Point2D & { z: number };
const point2d: Point2D = { x: 0, y: 0 };
const point3d: Point3D = { y: 0, z: 0 }; // Cannot assign object literal to `point3d` because property `x` is missing in object literal [1] but exists in `Point2D` [2].
Note that both cases are not exact shapes.
Is Flow's behavior in case of spread notation intentional in this case? Am I missing something?
See this issue.
The short version is that you can resolve basically all of these sorts of issues by making your objects exact. In general I've found that you'll have a much easier time with object types when making them exact as a rule unless you really don't want them to be exact for some reason. Also, $ReadOnly where applicable. Try
In Flow, why would one use a class versus a type?
type Point = {x: number; y: number};
class Point = {x: number; y: number};
In your example, a type is all you need.
But if you want to define methods, you'll want to use a class.
class Point {
x: number;
y: number;
constructor(x, y) {this.x = x; this.y = y;}
distance_from_origin(): number { /* code goes here */ }
angle_from_origin(): number { /* code goes here */ }
}
p: Point = new Point(2, 3);
d = p.distance_from_origin()
Types are a flow features for compile-time checking, to help you catch errors in your code. They are entirely stripped from the code before running it.
Classes aren't a Flow feature at all (Although Flow understands classes - every class you create also defines a Flow type) - they're a feature of ES6, the next version of JavaScript. Babel, the same program that strips Flow types from your code to make it valid JavaScript, can also convert your classes to ES5-compatible code.
Classes provide nominal typing, while object types provide structural typing.
Suppose I wanted to introduce a Vector type with x and y fields. When I go to create my add(p: Point, v: Vector): Point function, structural typing proves inadequate, e.g.
type Point = {x: number, y: number};
type Vector = {x: number, y: number};
function add(p: Point, v: Vector): Point {
return {x: p.x + v.x, y: p.y + v.y};
}
const p1: Point = {x:0, y:5};
const p2: Point = {x:2, y:3};
const v: Vector = add(p1, p2); // This is not an error in Flow
Contrast that against the nominally typed version with classes:
class Point { x: number; y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Vector { x: number; y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
function add(p: Point, v: Vector): Point {
return new Point(p.x + v.x, p.y + v.y);
}
const p1: Point = new Point(0, 5);
const p2: Point = new Point(2, 3);
const v: Vector = add(p1, p2); // Error: p2 isn't a Vector
(In reality you'd probably attach add as a method on the point class, but I've left it separate for parallel structure with the object type example.)
Note that you can use a tag field to get some semblance of nominal typing from object types, e.g.
type Point = { tag: "point", x: number y: number };
type Vector = { tag: "vector", x: number, y: number };
If your class doesn't have any methods, then I would suggest that this is the way to go.
As seen in the documentation, flow treats objects and classes differently. (And even if you are transpiring to ES5, flow checking happens before that.)
A class is compared by name. An object type is compared by structure.
In fact, the type alias is just a shorter way of writing the type structure annotation. It is like a variable containing the longer expression.
type A = {wow: number}
type B = {wow: number}
let a:A = {wow: 1}
let b:B = {wow: 2}
; [a, b] = [b, a] // fine
console.log(a, b) // > { wow: 2 } { wow: 1 }
class C {
constructor (x: number) { this.wow = x }
wow: number
}
class D {
constructor (x: number) { this.wow = x }
wow: number
}
let c:C = new C(3)
let d:D = new D(4)
c = d // ERROR: D This type is incompatible with C
d = b // ERROR: object type This type is incompatible with D
As you can see, two classes with identical structure are not compatible.
NOTE: There are ways to make different classes compatible, eg Union Types or Interfaces
I was under the impression that the assignment to entry in nil map error would only happen if we would want to assign to a double map, that is, when a map on a deeper level is trying to be assigned while the higher one doesn't exist, e.g.:
var mm map[int]map[int]int
mm[1][2] = 3
But it also happens for a simple map (though with struct as a key):
package main
import "fmt"
type COO struct {
x int
y int
}
var neighbours map[COO][]COO
func main() {
for i := 0; i < 30; i++ {
for j := 0; j < 20; j++ {
var buds []COO
if i < 29 {
buds = append(buds, COO{x: i + 1, y: j})
}
if i > 0 {
buds = append(buds, COO{x: i - 1, y: j})
}
if j < 19 {
buds = append(buds, COO{x: i, y: j + 1})
}
if j > 0 {
buds = append(buds, COO{x: i, y: j - 1})
}
neighbours[COO{x: i, y: j}] = buds // <--- yields error
}
}
fmt.Println(neighbours)
}
What could be wrong?
You need to initialize neighbours: var neighbours = make(map[COO][]COO)
See the second section in: https://blog.golang.org/go-maps-in-action
You'll get a panic whenever you try to insert a value into a map that hasn't been initialized.
In Golang, everything is initialized to a zero value, it's the default value for uninitialized variables.
So, as it has been conceived, a map's zero value is nil. When trying to use an non-initialized map, it panics. (Kind of a null pointer exception)
Sometimes it can be useful, because if you know the zero value of something you don't have to initialize it explicitly:
var str string
str += "42"
fmt.Println(str)
// 42 ; A string zero value is ""
var i int
i++
fmt.Println(i)
// 1 ; An int zero value is 0
var b bool
b = !b
fmt.Println(b)
// true ; A bool zero value is false
If you have a Java background, that's the same thing: primitive types have a default value and objects are initialized to null;
Now, for more complex types like chan and map, the zero value is nil, that's why you have to use make to instantiate them. Pointers also have a nil zero value. The case of arrays and slice is a bit more tricky:
var a [2]int
fmt.Println(a)
// [0 0]
var b []int
fmt.Println(b)
// [] ; initialized to an empty slice
The compiler knows the length of the array (it cannot be changed) and its type, so it can already instantiate the right amount of memory. All of the values are initialized to their zero value (unlike C where you can have anything inside your array). For the slice, it is initialized to the empty slice [], so you can use append normally.
Now, for structs, it is the same as for arrays. Go creates a struct with all its fields initialized to zero values. It makes a deep initialization, example here:
type Point struct {
x int
y int
}
type Line struct {
a Point
b Point
}
func main() {
var line Line
// the %#v format prints Golang's deep representation of a value
fmt.Printf("%#v\n", line)
}
// main.Line{a:main.Point{x:0, y:0}, b:main.Point{x:0, y:0}}
Finally, the interface and func types are also initialized to nil.
That's really all there is to it. When working with complex types, you just have to remember to initialize them. The only exception is for arrays because you can't do make([2]int).
In your case, you have map of slice, so you need at least two steps to put something inside: Initialize the nested slice, and initialize the first map:
var buds []COO
neighbours := make(map[COO][]COO)
neighbours[COO{}] = buds
// alternative (shorter)
neighbours := make(map[COO][]COO)
// You have to use equal here because the type of neighbours[0] is known
neighbours[COO{}] = make([]COO, 0)
I am using rickshaw graph to visualize my time series.
In the tutorial, we need data pair (x, y), where y has to be type double or int to plot like:
var data = [ { x: -1893456000, y: 92228531 }, { x: -1577923200, y: 106021568 }, { x: -1262304000, y: 123202660 } ];
is there anyway I can put string at y, like
var data = [ { x: -1893456000, y: "ID12" }, { x: -1577923200, y: "ID10" }, { x: -1262304000, y: "ID8" } ];
because I want to use the example http://code.shutterstock.com/rickshaw/examples/hover.html
to show the timeseries of IDs when mouse over.
Thanks
I dont think it works this way. But what you can do is, use a numeric Y value (eg 12 for ID12) and use a yFormatter in your hoverDetail:
var hoverDetail = new Rickshaw.Graph.HoverDetail({graph: graph,
xFormatter: function(x) {return x},
yFormatter: function(y) {return "ID"+y}
});
I think this should do the Trick