R/transform.R

Defines functions cartesian_to_xy flip_theta_back flip_theta solve_theta_from_spiral_length get_theta_from_x convert_y_to_height convert_height_to_y polar_to_x circular_extend_on_x radial_extend spiral_lines_expand cartesian_to_polar polar_to_cartesian xy_to_polar xy_to_cartesian

Documented in cartesian_to_xy polar_to_cartesian solve_theta_from_spiral_length xy_to_cartesian xy_to_polar

# == title
# Convert data coordinates to the canvas coordinates
#
# == param
# -x X-locations of the data points.
# -y Y-locations of the data points.
# -track_index Index of the track. 
#
# == details
# The canvas coordinates correspond to the "native" coordinates of the viewport where the graphics are to be drawn.
#
# Note different settings of ``flip`` and ``reverse`` in `spiral_initialize` affect the conversion.
#
# == value
# A data frame with two columns: x and y.
xy_to_cartesian = function(x, y, track_index = current_track_index()) {
	df_polar = xy_to_polar(x, y, track_index = track_index)
	polar_to_cartesian(df_polar$theta, df_polar$r)
}

# == title
# Convert data coordinates to polar coordinates
#
# == param
# -x X-locations of the data points.
# -y Y-locations of the data points.
# -track_index Index of the track. 
# -flip If it is FALSE, it returns theta for the original spiral (before flipping).
#
# == details
# Note different settings of ``flip`` and ``reverse`` in `spiral_initialize` affect the conversion.
#
# == value
# A data frame with two columns: theta (in radians) and r (the radius).
xy_to_polar = function(x, y, track_index = current_track_index(), flip = TRUE) {

	if(track_index == 0) {
		stop_wrap("No track has been created. Maybe you need to call `spiral_track()` first.")
	}

	spiral = spiral_env$spiral

	if(spiral$scale_by == "angle") {
		theta = (x - spiral$xlim[1]) * spiral$theta_range/spiral$xrange + spiral$theta_lim[1]

		if(spiral$reverse) {
			theta = spiral$theta_lim[2] - (theta - spiral$theta_lim[1])
		}
	} else if(spiral$scale_by == "curve_length") {
		if(spiral$reverse) {
			len = (spiral$xlim[2] - x) * spiral$spiral_length_range/spiral$xrange + spiral$spiral_length_lim[1]
		} else {
			len = (x - spiral$xlim[1]) * spiral$spiral_length_range/spiral$xrange + spiral$spiral_length_lim[1]
		}
		theta = solve_theta_from_spiral_length(len)
	}

	ymin = get_track_data("ymin", track_index)
	ymax = get_track_data("ymax", track_index)
	rmin = get_track_data("rmin", track_index)
	rmax = get_track_data("rmax", track_index)
	reverse_y = get_track_data("reverse_y", track_index)
	if(reverse_y) {
		d = rmax - (y - ymin) * (rmax - rmin)/(ymax - ymin) 
	} else {
		d = (y - ymin) * (rmax - rmin)/(ymax - ymin) + rmin
	}
	r = spiral$curve(theta) + d

	if(flip) theta = flip_theta(theta)

	data.frame(theta = theta, r = r)
}

# == title
# Convert polar coordinates to catersian coordinates
#
# == param
# -theta Angles, in radians.
# -r Radius.
#
# == value
# A data frame with two columns: x abd y.
polar_to_cartesian = function(theta, r) {
	x = cos(theta)*r
	y = sin(theta)*r
	
	data.frame(x = x, y = y)
}

# note here polar can not be directly used in spirals
cartesian_to_polar = function(x, y) {
	theta = atan(y/x)
	r = sqrt(x*x + y*y)

	data.frame(theta = theta, r = r)
}

# split a spiral line into d seguments
# return in cartesian coordinates
spiral_lines_expand = function(x, y, track_index = current_track_index()) {

	df = xy_to_polar(x, y, track_index = track_index)
	n = nrow(df)

	## here x and y are theta and r
	x = df$theta
	y = df$r
	nx = x[1]
	ny = y[1]
	min_segment_len = spiral_opt$min_segment_len
	for(i in seq_len(n)) {
		if(i == 1) {
			next
		}
		if(is.na(x[i]) || is.na(y[i])) {
            nx = c(nx, NA)
            ny = c(ny, NA)
            next
        }
        if(is.na(x[i-1]) || is.na(y[i-1])) {
            next
        }

        d = min_segment_len/(6*pi)
        if(x[i-1] <= 4*pi) {
        	d = min_segment_len
        } else {
        	d = min_segment_len*(4*pi/x[i-1])
        }

        if(abs(x[i] - x[i-1]) <= d) {
        	nc = 2
        } else {
        	nc = ceiling(abs(x[i] - x[i-1])/d)
        }

        nx = c(nx, seq(x[i-1], x[i], length.out = nc)[-1])
        ny = c(ny, seq(y[i-1], y[i], length.out = nc)[-1])
	}

	polar_to_cartesian(nx, ny)
}


# return in cartesian coordinates
radial_extend = function(x, y, offset, track_index = current_track_index()) {
	df = xy_to_polar(x, y, track_index = track_index)
	offset_x = convertWidth(offset*cos(df$theta), "native", valueOnly = TRUE)
	offset_y = convertHeight(offset*sin(df$theta), "native", valueOnly = TRUE)
	df = polar_to_cartesian(df$theta, df$r)
	df2 = df
	df2$x = df2$x + offset_x
	df2$y = df2$y + offset_y

	df2
}


# in xy coordinates
circular_extend_on_x = function(x, y, offset, track_index = track_index, coordinate = "polar") {
	spiral = spiral_env$spiral

	if(is.unit(offset)) {
		offset = convertWidth(offset, "native", valueOnly = TRUE)
	}

	df = xy_to_polar(x, y, track_index = track_index)
	if(spiral$scale_by == "curve_length") {
		v_offset = convert_y_to_height(y, track_index = track_index)
		x_offset = offset/(spiral$spiral_length(spiral$theta_lim[2], v_offset) - spiral$spiral_length(spiral$theta_lim[1], v_offset))*spiral$xrange

		df$theta = flip_theta_back(df$theta)
		t = solve_theta_from_spiral_length(spiral$spiral_length(df$theta, v_offset) + offset, 
				c(spiral$spiral_length(-spiral$theta_lim[2], max(v_offset)), 
				  spiral$spiral_length(2*spiral$theta_lim[2], max(v_offset))),
				v_offset)
		t = flip_theta(t)
		if(coordinate == "polar") {
			t
		} else {
			polar_to_x(t)
		}
	} else {
		v_offset = convert_y_to_height(y, track_index = track_index)
		df$theta = flip_theta_back(df$theta)
		t = solve_theta_from_spiral_length(spiral$spiral_length(df$theta, v_offset) + offset, 
				c(spiral$spiral_length(-spiral$theta_lim[2], max(v_offset)), 
				  spiral$spiral_length(2*spiral$theta_lim[2], max(v_offset))),
				v_offset)
		t = flip_theta(t)
		if(coordinate == "polar") {
			t
		} else {
			polar_to_x(t)
		}
	}
}

# given theta in spiral, it returns the corresponding x
polar_to_x = function(theta) {

	spiral = spiral_env$spiral

	theta = flip_theta_back(theta)

	if(spiral$scale_by == "angle") {
		x = (theta - spiral$theta_lim[1])/spiral$theta_range*spiral$xrange + spiral$xlim[1]
	} else {
		x = (spiral$spiral_length(theta) - spiral$spiral_length_lim[1])/spiral$spiral_length_range*spiral$xrange + spiral$xlim[1]
	}

	if(spiral$reverse) {
		x = spiral$xlim[2] - x + spiral$xlim[1]
	}
	x
}

# given a offset in absolute unit, it returns the offset in xy coordinates
convert_height_to_y = function(offset, track_index = current_track_index()) {
	offset = convertWidth(offset, "native", valueOnly = TRUE)

	ymin = get_track_data("ymin", track_index)
	ymax = get_track_data("ymax", track_index)
	rmin = get_track_data("rmin", track_index)
	rmax = get_track_data("rmax", track_index)

	offset/(rmax - rmin)*(ymax - ymin)
}

# here `height` is the offset to the base spiral
convert_y_to_height = function(y, track_index = current_track_index()) {
	spiral = spiral_env$spiral

	ymin = get_track_data("ymin", track_index)
	ymax = get_track_data("ymax", track_index)
	rmin = get_track_data("rmin", track_index)
	rmax = get_track_data("rmax", track_index)
	reverse_y = get_track_data("reverse_y", track_index)
	
	if(reverse_y) {
		v_offset = (ymax - y)/(ymax - ymin)*(rmax - rmin)
	} else {
		v_offset = (y - ymin)/(ymax - ymin)*(rmax - rmin)
	}
	if(track_index > 1) {
		for(i in 1:(track_index - 1)) {
			v_offset = v_offset + get_track_data("rmax", i) - get_track_data("rmin", i)
		}
	}
	v_offset
}


get_theta_from_x = function(x, ...) {
	xy_to_polar(x, rep(0, length(x)), ...)$theta
}

# == title
# Get theta from given spiral lengths
#
# == param
# -len A vector of spiral lengths.
# -interval Interval to search for the solution.
# -offset Offset of the spiral. In the general form: ``r = a + r*theta``, offset is the value of ``a``.
#
# == details
# The length of the spiral has a complicated form, see https://downloads.imagej.net/fiji/snapshots/arc_length.pdf .
# Let's say the form is ``l = f(theta)``, `solve_theta_from_spiral_length` tries to find theta by a known ``l``.
# It uses `stats::uniroot` to search solutions.
#
# == value
# The theta value.
#
# == example
# spiral_initialize()
# s = current_spiral()
# theta = pi*seq(2, 3, length = 10)
# len = s$spiral_length(theta)
# solve_theta_from_spiral_length(len) # should be very similar as theta
solve_theta_from_spiral_length = function(len, interval = NULL, offset = 0) {
	n = length(len)
	theta = numeric(n)

	spiral = spiral_env$spiral
	if(is.null(interval)) {
		interval = c(spiral$theta_lim[1] - spiral$theta_range, spiral$theta_lim[2] + spiral$theta_range)
	}

	offset = offset
	if(length(offset) == 1) offset = rep(offset, n)
	for(i in seq_len(n)) {
		theta[i] = uniroot(function(theta, a) {
			spiral$spiral_length(theta, offset[i]) - a
		}, interval = interval, a = len[i])$root
	}

	theta
}


flip_theta = function(theta) {
	spiral = spiral_env$spiral

	if(spiral$flip == "horizontal") {
		theta = pi - theta 
	} else if(spiral$flip == "vertical") {
		theta = 2*pi - theta
	} else if(spiral$flip == "both") {
		theta = theta - pi
	} else {
		theta
	}
}

flip_theta_back = function(theta) {
	spiral = spiral_env$spiral

	if(spiral$flip == "horizontal") {
		theta = pi - theta 
	} else if(spiral$flip == "vertical") {
		theta = 2*pi - theta
	} else if(spiral$flip == "both") {
		theta = theta + pi
	} else {
		theta
	}
	theta
}

# == title
# Convert canvas coordinates to the data coordinates
#
# == param
# -x X-locations of the data points in canvas coordinates.
# -y Y-locations of the data points in canvas coordinates.
# -track_index Index of the track. 
#
# == details
# The data points are assigned to the nearest inner loops. Denote the a data point has a coordinate (r, theta)
# in the polar coordinate system, r_k and r_{k+1} are the radius of the two loops at theta + 2*pi*a and theta + 2*pi*(a+1) that below and above the data point,
# the data point is assigned to the loop k.
#
# == value
# A data frame with two columns: x and y.
#
# == example
# x = runif(100, -5, 5)
# y = runif(100, -5, 5)
# spiral_initialize()
# spiral_track()
# df = cartesian_to_xy(x, y)
# # directly draw in the viewport
# grid.points(x, y, default.units="native")
# # check whether the converted xy are correct (should overlap to the previous points)
# spiral_points(df$x, df$y, pch = 16)
cartesian_to_xy = function(x, y, track_index = current_track_index()) {

	if(length(x) > 1) {
		n = length(x)

		df = data.frame(x = numeric(n), y = numeric(n))

		for(i in seq_len(n)) {
			df2 = cartesian_to_xy(x[i], y[i], track_index)
			df[i, 1] = df2[1, 1]
			df[i, 2] = df2[1, 2]
		}

		return(df)
	}

	s = spiral_env$spiral
	r0 = sqrt(x^2 + y^2)

	if(r0 == 0) {
		theta = mean(s$theta_lim)
	} else {

		# make sure alpha is between [0, 2pi)
		alpha = atan(y/x)
		if(y >= 0 & x < 0) {
			alpha = alpha + pi
		} else if(y < 0 & x < 0) {
			alpha = alpha + pi
		} else if(x >= 0 & y < 0) {
			alpha = alpha + 2*pi
		}


		while(alpha < s$theta_lim[1]) {
			alpha = alpha + 2*pi
		}
		if(alpha > s$theta_lim[2]) {
			stop_wrap("The radial line the connects data point and the origin should intersect the spiral.")
		}
		t2 = seq(alpha, s$theta_lim[2], by = 2*pi)

		all_r = s$curve(t2)
		if(all_r[length(all_r)] <= r0) {
			theta = t2[length(t2)]
		} else if(all_r[1] >= r0) {
			theta = t2[1]
		} else {
			ind = which(all_r <= r0)
			theta = t2[ ind[length(ind)] ]
		}
	}

	x2 = polar_to_x(theta)
	d0 = r0 - s$curve(theta)

	ymin = get_track_data("ymin", track_index)
	ymax = get_track_data("ymax", track_index)
	rmin = get_track_data("rmin", track_index)
	rmax = get_track_data("rmax", track_index)
	reverse_y = get_track_data("reverse_y", track_index)

	if(reverse_y) {
		y2 = ymax - (d0 - rmin)/(rmax - rmin)*(ymax - ymin)
	} else {
		y2 = (d0 - rmin)/(rmax - rmin)*(ymax - ymin) + ymin
	}

	data.frame(x = x2, y = y2)
}

Try the spiralize package in your browser

Any scripts or data that you put into this service are public.

spiralize documentation built on Feb. 6, 2022, 1:06 a.m.