peak detection for growing time series using Swift - swift

Would anyone have a good algorithm to measure peaks in growing time series data using Swift (v3)? So, detect peaks as the data is streaming in.
E.g. a Swift version of the smooth z-wave algorithm. That algorithm seems to be suitable.
I would need to detect the peaks as shown below. The data contains positive and negative numbers. Output should be a counter of the peaks, and/or true/false for that specific sample.
Sample dataset (summary of the last series):
let samples = [0.01, -0.02, -0.02, 0.01, -0.01, -0.01, 0.00, 0.10, 0.31,
-0.10, -0.73, -0.68, 0.21, 1.22, 0.67, -0.59, -1.04, 0.06, 0.42, 0.07,
0.03, -0.18, 0.11, -0.06, -0.02, 0.16, 0.21, 0.03, -0.68, -0.89, 0.18,
1.31, 0.66, 0.07, -1.62, -0.16, 0.67, 0.19, -0.42, 0.23, -0.05, -0.01,
0.03, 0.06, 0.27, 0.15, -0.50, -1.18, 0.11, 1.30, 0.93, 0.16, -1.32,
-0.10, 0.55, 0.23, -0.03, -0.23, 0.16, -0.04, 0.01, 0.12, 0.35, -0.38,
-1.11, 0.07, 1.46, 0.61, -0.68, -1.16, 0.29, 0.54, -0.05, 0.02, -0.01,
0.12, 0.23, 0.29, -0.75, -0.95, 0.11, 1.51, 0.70, -0.30, -1.48, 0.13,
0.50, 0.18, -0.06, -0.01, -0.02, 0.03, -0.02, 0.06, 0.03, 0.03, 0.02,
-0.01, 0.01, 0.02, 0.01]
Update: Thanks to Jean-Paul for the initial Swift port. But not sure the z-wave algo is the right one for this dataset. lag=10,threshold=3,influence=0.2 works fine for the last series of the dataset, but I have not been able to find a combination that comes close for the complete dataset.
The issues: with a big lag the first data samples are not included, I need one signal per peak and the algorithm would need further work to be made more efficient.
E.g. result for full dataset, using the Python code, and (e.g.) lag=5,threshold=2.5,influence=0.7 is missing peaks for series 1 and 2, and showing too many false positives in the quiet periods:
Full dataset (should result in 25 peaks):
let samples = [-1.38, -0.97, -1.20, -2.06, -2.26, -0.99, 0.11, -0.47, -0.95, -2.61, -0.88, -0.74, -1.12, -1.19, -1.12, -1.04, -0.72, -1.21, -2.61, -1.41, -0.23, -0.27, -0.43, -1.77, -2.75, -0.61, -0.73, -1.53, -1.02, -1.14, -1.12, -1.06, -0.78, -0.72, -2.41, -1.55, -0.01, -0.44, -0.47, -2.02, -1.66, -0.43, -0.93, -1.51, -0.86, -1.06, -1.10, -0.88, -0.84, -1.26, -2.59, -0.92, 0.29, -0.50, -1.31, -2.40, -0.88, -0.56, -1.09, -1.14, -1.09, -0.90, -0.99, -0.84, -0.75, -2.59, -1.34, -0.08, -0.36, -0.50, -1.89, -1.60, -0.55, -0.78, -1.46, -0.96, -0.97, -1.18, -0.98, -1.10, -1.07, -1.06, -1.79, -1.78, -1.54, -1.25, -1.00, -0.46, -0.27, -0.20, -0.15, -0.13, -0.11, -0.13, -0.09, -0.09, -0.05, 0.02, 0.20, -0.31, -1.35, -0.03, 1.34, 0.52, 0.80, -0.91, -1.26, -0.10, -0.10, 0.53, 0.93, 0.60, -0.83, -1.87, -0.21, 1.26, 0.44, 0.86, 0.73, -2.05, -1.66, 0.31, 1.04, 0.72, 0.63, -0.01, -2.14, -0.48, 0.77, 0.63, 0.58, 0.66, -1.01, -1.28, 0.18, 0.44, 0.09, -0.27, -0.06, 0.06, -0.18, -0.01, -0.08, -0.07, -0.06, -0.06, -0.07, -0.07, -0.06, -0.05, -0.04, -0.03, -0.02, -0.02, -0.03, -0.03, -0.01, 0.01, 0.00, 0.01, 0.05, 0.12, 0.16, 0.25, 0.29, -0.16, -0.69, -1.05, -0.84, -0.54, -0.07, 0.46, 1.12, 1.05, 0.77, 0.68, 0.63, 0.39, -0.96, -1.61, -0.68, -0.14, -0.03, 0.22, 0.31, 0.15, -0.02, 0.11, 0.14, 0.00, 0.04, 0.18, 0.27, 0.14, -0.05, -0.03, -0.08, -0.41, -0.94, -1.03, -0.50, 0.02, 0.52, 1.10, 1.03, 0.79, 0.69, 0.55, -0.34, -1.17, -0.89, -0.54, -0.22, 0.37, 0.47, 0.39, 0.23, 0.00, -0.02, 0.05, 0.10, 0.12, 0.09, 0.05, -0.12, -0.50, -0.89, -0.89, -0.48, 0.00, 0.43, 1.03, 0.95, 0.67, 0.64, 0.47, -0.07, -0.85, -1.02, -0.73, -0.08, 0.38, 0.46, 0.32, 0.15, 0.01, -0.01, 0.09, 0.20, 0.23, 0.19, 0.12, -0.50, -1.17, -0.97, -0.12, 0.15, 0.70, 1.31, 0.97, 0.45, 0.27, -0.73, -1.00, -0.52, -0.27, 0.10, 0.33, 0.34, 0.23, 0.07, -0.04, -0.27, -0.24, 0.10, 0.21, 0.05, -0.07, 0.04, 0.21, 0.29, 0.16, -0.45, -1.13, -0.93, -0.28, 0.04, 0.72, 1.35, 1.05, 0.56, 0.43, 0.17, -0.59, -1.38, -0.76, 0.10, 0.44, 0.46, 0.35, 0.12, -0.07, -0.05, -0.01, -0.07, -0.04, 0.01, 0.01, 0.06, 0.02, -0.03, -0.05, 0.00, 0.01, -0.02, -0.03, -0.02, -0.01, 0.00, -0.01, 0.00, -0.01, 0.00, -0.01, -0.01, 0.00, 0.01, -0.01, -0.01, 0.00, 0.00, 0.01, 0.01, 0.01, 0.04, 0.06, 0.05, 0.05, 0.04, 0.03, 0.00, -0.12, -0.16, -0.09, -0.01, 0.14, 0.07, 0.06, 0.00, -0.03, 0.00, 0.06, 0.06, -0.04, -0.11, -0.02, 0.13, 0.18, 0.21, 0.01, -0.31, -0.92, -1.35, -0.62, 0.03, 0.78, 1.36, 1.07, 0.59, 0.75, 0.42, -1.65, -3.16, -0.97, 0.24, 1.44, 1.50, 0.84, 0.47, 0.56, 0.40, -1.50, -2.71, -1.22, 0.01, 1.20, 1.55, 0.92, 0.44, 0.66, 0.73, -0.43, -2.34, -2.28, -0.72, 0.36, 1.41, 1.56, 0.89, 0.54, 0.67, 0.39, -1.78, -2.75, -1.07, -0.07, 1.16, 1.65, 0.80, 0.47, 0.73, 0.86, -0.24, -1.52, -1.68, -0.39, 0.02, 0.38, 0.60, 0.49, 0.02, -0.42, -0.31, -0.01, 0.08, 0.00, -0.07, -0.05, -0.01, -0.02, -0.04, -0.05, -0.02, -0.01, -0.02, -0.02, -0.03, -0.05, -0.04, -0.03, -0.01, -0.01, 0.00, -0.01, 0.00, 0.01, 0.00, 0.00, 0.00, 0.01, 0.01, -0.01, -0.03, -0.02, -0.01, 0.00, 0.00, 0.00, -0.01, 0.01, 0.00, -0.01, 0.02, 0.07, 0.15, 0.28, 0.31, 0.08, -0.26, -0.54, -0.96, -1.08, -0.27, 0.01, 0.45, 1.18, 1.07, 0.71, 0.65, 0.20, -0.80, -1.30, -0.74, -0.24, 0.29, 0.47, 0.34, 0.15, 0.02, 0.03, -0.02, -0.16, -0.13, 0.05, 0.09, -0.01, -0.08, -0.06, 0.03, 0.13, 0.19, 0.23, 0.18, 0.10, -0.07, -0.44, -0.91, -1.05, -0.64, -0.08, 0.50, 1.12, 1.35, 0.89, 0.58, 0.54, -0.58, -1.27, -1.20, -0.48, 0.19, 0.62, 0.62, 0.37, -0.01, -0.35, -0.33, 0.07, 0.29, 0.10, -0.14, -0.10, 0.07, 0.07, 0.01, 0.03, 0.09, 0.20, 0.32, 0.26, -0.02, -0.32, -0.78, -1.25, -0.93, -0.16, 0.30, 0.88, 1.40, 1.14, 0.72, 0.48, -0.54, -1.21, -1.13, -0.41, 0.18, 0.51, 0.53, 0.36, 0.11, -0.03, -0.09, -0.28, -0.11, 0.11, 0.15, 0.04, -0.08, -0.04, 0.04, 0.09, 0.16, 0.26, 0.43, 0.09, -0.88, -1.46, -0.64, -0.16, 0.43, 1.37, 1.34, 0.84, 0.52, -0.17, -0.87, -1.22, -0.76, 0.03, 0.47, 0.60, 0.36, 0.04, -0.09, -0.03, 0.02, -0.04, 0.04, 0.12, 0.13, 0.19, 0.27, 0.31, 0.18, -0.42, -0.99, -1.13, -0.75, -0.22, 0.50, 1.42, 1.41, 0.98, 0.51, 0.29, -0.69, -1.59, -0.88, -0.13, 0.31, 0.49, 0.46, 0.30, 0.05, -0.08, -0.03, 0.01, -0.04, -0.06, 0.02, 0.03, 0.01, -0.02, 0.01, 0.04, 0.06, 0.04, 0.03, 0.02, 0.03, 0.03, 0.01, -0.01, 0.00, 0.02, 0.00, 0.02, 0.02, 0.02, -0.02, -0.01, 0.02, 0.02, 0.01, 0.02, 0.02, 0.02, 0.02, 0.04, 0.03, 0.01, 0.01, 0.02, 0.01, 0.01, 0.01, 0.02, 0.01, 0.00, 0.01, 0.01, 0.00, 0.00, 0.01, 0.00, 0.00, 0.01, 0.00, 0.02, 0.00, 0.00, 0.01, 0.01, 0.00, 0.00, 0.01, 0.01, 0.00, 0.00, 0.00, 0.01, 0.01, 0.00, 0.01, 0.00, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]
I am therefore not sure the z-wave algorithm is the right approach for this kind of dataset.

Translation of smooth z-score algo into Swift
Well, to quickly help you out: here is a translation of the algo into Swift: Demo in Swift Sandbox
Warning: I am by no means a swift programmer, so there could be mistakes in there!
Also note that I have turned off negative signals, as for OP's purpose we only want positive signals.
Swift code:
import Glibc // or Darwin/ Foundation/ Cocoa/ UIKit (depending on OS)
// Function to calculate the arithmetic mean
func arithmeticMean(array: [Double]) -> Double {
var total: Double = 0
for number in array {
total += number
}
return total / Double(array.count)
}
// Function to calculate the standard deviation
func standardDeviation(array: [Double]) -> Double
{
let length = Double(array.count)
let avg = array.reduce(0, {$0 + $1}) / length
let sumOfSquaredAvgDiff = array.map { pow($0 - avg, 2.0)}.reduce(0, {$0 + $1})
return sqrt(sumOfSquaredAvgDiff / length)
}
// Function to extract some range from an array
func subArray<T>(array: [T], s: Int, e: Int) -> [T] {
if e > array.count {
return []
}
return Array(array[s..<min(e, array.count)])
}
// Smooth z-score thresholding filter
func ThresholdingAlgo(y: [Double],lag: Int,threshold: Double,influence: Double) -> ([Int],[Double],[Double]) {
// Create arrays
var signals = Array(repeating: 0, count: y.count)
var filteredY = Array(repeating: 0.0, count: y.count)
var avgFilter = Array(repeating: 0.0, count: y.count)
var stdFilter = Array(repeating: 0.0, count: y.count)
// Initialise variables
for i in 0...lag-1 {
signals[i] = 0
filteredY[i] = y[i]
}
// Start filter
avgFilter[lag-1] = arithmeticMean(array: subArray(array: y, s: 0, e: lag-1))
stdFilter[lag-1] = standardDeviation(array: subArray(array: y, s: 0, e: lag-1))
for i in lag...y.count-1 {
if abs(y[i] - avgFilter[i-1]) > threshold*stdFilter[i-1] {
if y[i] > avgFilter[i-1] {
signals[i] = 1 // Positive signal
} else {
// Negative signals are turned off for this application
//signals[i] = -1 // Negative signal
}
filteredY[i] = influence*y[i] + (1-influence)*filteredY[i-1]
} else {
signals[i] = 0 // No signal
filteredY[i] = y[i]
}
// Adjust the filters
avgFilter[i] = arithmeticMean(array: subArray(array: filteredY, s: i-lag, e: i))
stdFilter[i] = standardDeviation(array: subArray(array: filteredY, s: i-lag, e: i))
}
return (signals,avgFilter,stdFilter)
}
// Demo
let samples = [0.01, -0.02, -0.02, 0.01, -0.01, -0.01, 0.00, 0.10, 0.31,
-0.10, -0.73, -0.68, 0.21, 1.22, 0.67, -0.59, -1.04, 0.06, 0.42, 0.07,
0.03, -0.18, 0.11, -0.06, -0.02, 0.16, 0.21, 0.03, -0.68, -0.89, 0.18,
1.31, 0.66, 0.07, -1.62, -0.16, 0.67, 0.19, -0.42, 0.23, -0.05, -0.01,
0.03, 0.06, 0.27, 0.15, -0.50, -1.18, 0.11, 1.30, 0.93, 0.16, -1.32,
-0.10, 0.55, 0.23, -0.03, -0.23, 0.16, -0.04, 0.01, 0.12, 0.35, -0.38,
-1.11, 0.07, 1.46, 0.61, -0.68, -1.16, 0.29, 0.54, -0.05, 0.02, -0.01,
0.12, 0.23, 0.29, -0.75, -0.95, 0.11, 1.51, 0.70, -0.30, -1.48, 0.13,
0.50, 0.18, -0.06, -0.01, -0.02, 0.03, -0.02, 0.06, 0.03, 0.03, 0.02,
-0.01, 0.01, 0.02, 0.01]
// Run filter
let (signals,avgFilter,stdFilter) = ThresholdingAlgo(y: samples, lag: 10, threshold: 3, influence: 0.2)
// Print output to console
print("\nOutput: \n ")
for i in 0...signals.count - 1 {
print("Data point \(i)\t\t sample: \(samples[i]) \t signal: \(signals[i])\n")
}
// Raw data for creating a plot in Excel
print("\n \n Raw data for creating a plot in Excel: \n ")
for i in 0...signals.count - 1 {
print("\(i+1)\t\(samples[i])\t\(signals[i])\t\(avgFilter[i])\t\(stdFilter[i])\n")
}
With the result for the sample data (for lag = 10, threshold = 3, influence = 0.2):
Update
You can improve the performance of the algorithm by using different values for the lag of the mean and the standard deviation. E.g.:
// Smooth z-score thresholding filter
func ThresholdingAlgo(y: [Double], lagMean: Int, lagStd: Int, threshold: Double, influenceMean: Double, influenceStd: Double) -> ([Int],[Double],[Double]) {
// Create arrays
var signals = Array(repeating: 0, count: y.count)
var filteredYmean = Array(repeating: 0.0, count: y.count)
var filteredYstd = Array(repeating: 0.0, count: y.count)
var avgFilter = Array(repeating: 0.0, count: y.count)
var stdFilter = Array(repeating: 0.0, count: y.count)
// Initialise variables
for i in 0...lagMean-1 {
signals[i] = 0
filteredYmean[i] = y[i]
filteredYstd[i] = y[i]
}
// Start filter
avgFilter[lagMean-1] = arithmeticMean(array: subArray(array: y, s: 0, e: lagMean-1))
stdFilter[lagStd-1] = standardDeviation(array: subArray(array: y, s: 0, e: lagStd-1))
for i in max(lagMean,lagStd)...y.count-1 {
if abs(y[i] - avgFilter[i-1]) > threshold*stdFilter[i-1] {
if y[i] > avgFilter[i-1] {
signals[i] = 1 // Positive signal
} else {
signals[i] = -1 // Negative signal
}
filteredYmean[i] = influenceMean*y[i] + (1-influenceMean)*filteredYmean[i-1]
filteredYstd[i] = influenceStd*y[i] + (1-influenceStd)*filteredYstd[i-1]
} else {
signals[i] = 0 // No signal
filteredYmean[i] = y[i]
filteredYstd[i] = y[i]
}
// Adjust the filters
avgFilter[i] = arithmeticMean(array: subArray(array: filteredYmean, s: i-lagMean, e: i))
stdFilter[i] = standardDeviation(array: subArray(array: filteredYstd, s: i-lagStd, e: i))
}
return (signals,avgFilter,stdFilter)
}
Then using for example let (signals,avgFilter,stdFilter) = ThresholdingAlgo(y: samples, lagMean: 10, lagStd: 100, threshold: 2, influenceMean: 0.5, influenceStd: 0.1) can give a lot better results:
DEMO

Related

Optimal number of clusters - Error in FUNcluster(x, i, ...) : more cluster centers than distinct data points

I have these data and I need to find the optimal number of clusters of this table.
The values can be either 0, 0.5 or 1
library(NbClust)
library(factoextra)
library(pheatmap)
tab=structure(list(`57-B1` = c(1, 0.5, 0.5, 1, 1, 0.5), `57-B3` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `57-C1` = c(1, 0.5, 0.5, 0.5, 1, 0.5),
`57-C5` = c(1, 0.5, 0.5, 1, 1, 1), `57-H2` = c(1, 0.5, 0.5,
0, 1, 1), `57-H4` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5), `61-1-B1` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `61-1-C2` = c(0.5, 0.5, 0.5, 0, 0.5,
0.5), `61-1-C5` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5), `61-1-H1` = c(0.5,
0.5, 0, 0, 0.5, 0.5), `61-1-H3` = c(0.5, 0.5, 0.5, 0, 0.5,
0.5), `61-1-H5` = c(0.5, 0.5, 0, 0.5, 0.5, 0.5), `62-2_H2` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `62_1_C2` = c(0.5, 0.5, 0, 0.5, 0.5,
0.5), `62_1_C5` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5), `FL-39-C3` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `FL-41-1-C3` = c(0.5, 0.5, 0.5, 0,
0.5, 0.5), `FL-57-B1` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5), `FL-57-B2` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `FL-57-C2` = c(0.5, 0.5, 0, 0.5,
0.5, 0.5), `FL-57-C3` = c(1, 1, 1, 0, 1, 1), `FL-57-C5` = c(1,
0.5, 0.5, 1, 1, 1), `FL-57-H1` = c(1, 0.5, 0.5, 1, 1, 1),
`FL-57-H4` = c(0.5, 0.5, 0, 0, 0.5, 0.5), `FL-57-H5` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `FL-61-1-B1` = c(0.5, 0.5, 0.5, 0,
0.5, 0.5), `FL-61-1-B4` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5),
`FL-61-1-C2` = c(0.5, 0.5, 0, 0, 0.5, 0.5), `FL-61-1-C4` = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), `FL-61-1-H3` = c(0.5, 0.5, 0.5, 0,
0.5, 0.5), `FL-61-1-H4` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5),
`FL-61-1-H5` = c(0.5, 0.5, 0, 0.5, 0.5, 0.5), `FL-62-1-C3` = c(0.5,
0.5, 0, 0.5, 0.5, 0.5), `FL-62-2-H2` = c(0.5, 0.5, 0.5, 0,
0.5, 0.5), `FL-73-H1` = c(0.5, 0.5, 0.5, 0, 0.5, 0.5), P_57_F = c(0.5,
0.5, 0.5, 0, 0.5, 0.5), P_57_M = c(0.5, 0.5, 0.5, 0, 0.5,
0.5)), row.names = c("g1", "g2", "g3", "g4", "g5", "g6"), class = "data.frame")
I tried both on scaled and non-scale values:
fviz_nbclust(scale(tab), kmeans, method = "wss")
fviz_nbclust(tab, kmeans, method = "wss")
and I get this error:
Error in FUNcluster(x, i, ...) :
more cluster centers than distinct data points.
how can I fix it?
Many thanks for your help !
I maybe found the solutions: it was sufficient to specify k.max = any number lower than nrow(tab)

How to create an inner shadow for custom shape in Flutter?

Below is the custom shape I've want to give an inner shadow to :
Below is the code I've used to create this shape : (The text part is not included in the code)
class TitleContainerPaint extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
Paint x = Paint()..color = Colors.grey ..style = PaintingStyle.fill;
Path a = Path();
a.moveTo(size.height * 0.5, 0);
a.lineTo(size.width * 0.3, 0);
a.cubicTo(size.width * 0.325, 0, size.width * 0.325, size.height * 0.5 - 10, size.width * 0.35, size.height * 0.5 - 10);
a.lineTo(size.width * 0.825, size.height * 0.35);
a.cubicTo(size.width * 0.85, size.height * 0.5 - 10, size.width * 0.85, size.height * 0.15, size.width * 0.875, size.height * 0.15);
a.lineTo(size.width - size.height * 0.25, size.height * 0.15);
a.arcTo(Rect.fromCircle(center: Offset(size.width - size.height * 0.35,size.height * 0.5), radius: size.height * 0.35), -pi/2, pi, false);
a.lineTo(size.width * 0.875, size.height * 0.85);
a.cubicTo(size.width * 0.85, size.height * 0.85, size.width * 0.85, size.height * 0.5 + 10, size.width * 0.825, size.height * 0.5 + 10);
a.lineTo(size.width * 0.35, size.height * 0.65);
a.cubicTo(size.width * 0.325, size.height * 0.5 + 10, size.width * 0.325, size.height, size.width * 0.3, size.height);
a.lineTo(size.height * 0.5, size.height);
a.arcTo(Rect.fromCircle(center: Offset(size.height * 0.5,size.height * 0.5), radius: size.height * 0.5), pi/2, pi, false);
canvas.drawPath(a, x);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
As mentioned in the question, my goal is to add an inner shadow to this shape like the image below :
Can someone please help me achieve this?
Thankyou in advance.
Use your paint like this:
Paint x = Paint()
..style = PaintingStyle.fill
..maskFilter = MaskFilter.blur(BlurStyle.inner, 5)
..color = Colors.grey;
Output:
I propose the same approach as given in my other answer. In your case you just use the CustomPaint widget as the child for the inner shadow widget:
InnerShadow(
shadow: const BoxShadow(
blurRadius: 20,
color: Colors.black,
),
child: CustomPaint(painter: TitleContainerPaint()),
)
The complete code could be found here https://codepen.io/priezz/pen/abBRmrb
P.S. Your TitleContainerPaint gives slightly different shape than given in your example image, you'll probably need to tweak it. Maybe it's just the issue with Flutter for Web.
//change the alpha color of your grey color like this
canvas.drawShadow(path, Colors.grey.withAlpha(50), -4.0, false);
Here the shadow will be inner.

Swift animating view height

I am trying to chain 2 animations. But can not get the second animation (change of height) working.
1st try:
self.customView.frame = CGRect(x: 20, y: 0, width: width, height: 76)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.customView.frame = CGRect(x: 20, y: 20, width: 280, height: 76)
}, completion: { (finished: Bool) in
UIView.animate(withDuration: 0.3) {
self.customView.frame = CGRect(x: 20, y: 20, width: 280, height: 136)
}
})
2nd one:
self.customView.frame = CGRect(x: 20, y: 0, width: width, height: 76)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.customView.frame = CGRect(x: 20, y: 20, width: 280, height: 76)
}, completion: { (finished: Bool) in
UIView.animate(withDuration: 0.3) {
var heightConstraint = self.customView.heightAnchor.constraint(equalToConstant: 136)
heightConstraint.isActive = true
self.view.layoutIfNeeded()
}
})

How to fill color inside a shape created using CustomPainter drawPath?

So, I've created a shape using drawPath and drawArc from CustomPainter, the PaintingStyle is stroke, but when I change it to fill, it only fills the arcs and not the whole shape.
I want to fill the shape I created with a color, so how can I fill the shape with a particular color?
class CustomShapeCard extends CustomPainter {
CustomShapeCard({#required this.strokeWidth, #required this.color});
final double strokeWidth;
final Color color;
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
var path = Path();
path.moveTo(size.width * 0.1, size.height * 0.2);
path.lineTo(size.width * 0.1, size.height * 0.9);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.2) - 14, size.height * 0.9),
height: 50,
width: 50,
),
math.pi / 2,
math.pi / 2,
false,
paint,
);
path.moveTo((size.width * 0.2) - 14, (size.height * 0.9) + 25);
path.lineTo((size.width * 0.9) - 25, size.height * 0.9 + 25);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.9) - 25, size.height * 0.9),
height: 50,
width: 50,
),
math.pi / 2,
-math.pi / 2,
false,
paint,
);
path.moveTo((size.width * 0.9), (size.height * 0.9));
path.lineTo(size.width * 0.9, size.height * 0.35);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.9) - 25, size.height * 0.35),
height: 50,
width: 50,
),
-math.pi / 2,
math.pi / 2,
false,
paint,
);
path.moveTo((size.width * 0.9) - 25, (size.height * 0.35) - 25);
path.lineTo(size.width * 0.25, (size.height * 0.35) - 25);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.25), (size.height * 0.35) - 50),
height: 50,
width: 50,
),
math.pi / 2,
math.pi / 3,
false,
paint,
);
path.moveTo((size.width * 0.25) - 20, (size.height * 0.35) - 35);
path.lineTo(size.width * 0.1, size.height * 0.2);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
When the PaintingStyle is stroke, I get this,
When I change PaintingStyle to fill, I get,
To fill a shape like this with a color,
the arcToPoint() should be used instead of drawArc().

plotly R graph - marker symbol doesn't show in legend unless segment trace is added

I have a plotly graph with the following:
- line plot where censored values are plotted as open circles, and non-censored dataset points are solid.
- in some graphs a trend line will be shown (added using add_segment)
I can't seem to get the marker legend to show up when the trend line is not added.
Ideally, I would like only the 'censored datapoints' to show up in the legend, so I have added two traces for markers (1 for censored with showlegend = TRUE, the other with showlegend=FALSE). Not sure if there is another way to do this - very new to plot_ly.
# datasets
results <- structure(list(slope = 0, slope_p_val = 0.672652383888971, int_from_data = 0.06,
pct_cens = 9.3, annual_slope_units = 0, sigclass = "No evidence of trend",
sig = structure(2L, .Label = c("Significant", "Not significant"
), class = "factor"), slope_text = " ", trend_color = "#CBCBCB"), class = "data.frame", row.names = c(NA,
-1L))
dataset <- dput(dataset)
structure(list(Date = structure(c(12794, 12823, 12863, 12893,
12921, 12948, 12978, 13003, 13048, 13073, 13108, 13137, 13172,
13199, 13230, 13263, 13291, 13318, 13349, 13375, 13405, 13432,
13472, 13486, 13523, 13564, 13592, 13622, 13648, 13683, 13705,
13746, 13775, 13810, 13838, 13852, 13929, 13957, 13986, 14014,
14053, 14067, 14110, 14139, 14166, 14196, 14224, 14266, 14294,
14321, 14348, 14377, 14405, 14446, 14476, 14501, 14532, 14566,
14593, 14636, 14684, 14712, 14740, 14770, 14811, 14839, 14868,
14896, 14929, 14952, 14993, 15020, 15050, 15077, 15105, 15146,
15174, 15208, 15238, 15265, 15293, 15315, 15350, 15385, 15412,
15441, 15482, 15511, 15537, 15566, 15600, 15631, 15658, 15685,
15728, 15742, 15769, 15811, 15839, 15868, 15904, 15931, 15958,
16001, 16030, 16042, 16091, 16119, 16149, 16174, 16204, 16230,
16268, 16302, 16330, 16359, 16386, 16412), class = "Date"), cenTF = c(FALSE,
FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE), Value = c(0.05,
0.06, 0.025, 0.08, 0.06, 0.07, 0.05, 0.025, 0.025, 0.1, 0.12,
0.18, 0.14, 0.19, 0.36, 0.17, 0.09, 0.07, 0.05, 0.025, 0.05,
0.025, 0.08, 0.05, 0.06, 0.06, 0.06, 0.05, 0.07, 0.06, 0.05,
0.07, 0.06, 0.05, 0.05, 0.06, 0.05, 0.07, 0.1, 0.09, 0.025, 0.07,
0.07, 0.14, 0.17, 0.11, 0.1, 0.14, 0.17, 0.17, 0.18, 0.09, 0.08,
0.08, 0.1, 0.07, 0.07, 0.06, 0.025, 0.09, 0.07, 0.08, 0.06, 0.06,
0.06, 0.08, 0.06, 0.06, 0.05, 0.05, 0.06, 0.06, 0.11, 0.1, 0.1,
0.05, 0.06, 0.025, 0.06, 0.025, 0.06, 0.06, 0.07, 0.06, 0.05,
0.07, 0.05, 0.06, 0.05, 0.05, 0.06, 0.06, 0.025, 0.05, 0.06,
0.06, 0.06, 0.23, 0.06, 0.06, 0.06, 0.025, 0.05, 0.05, 0.1, 0.06,
0.06, 0.06, 0.07, 0.08, 0.06, 0.07, 0.06, 0.05, 0.07, 0.06, 0.06,
0.06)), row.names = c(NA, -118L), class = "data.frame")
plotly_1 <- function(dataset, results,
cen_var="cenTF", val_var="Value",
date_period =NULL
){
dataset <- dataset %>%
mutate(cenNM = case_when(!!ensym(cen_var) := FALSE ~ " ",
!!ensym(cen_var) := TRUE ~ "Value too low to detect"),
plotVal = !!ensym(val_var),
plotCen = !!ensym(cen_var),
pct_cens = round(sum(.data$plotCen, na.rm=TRUE)/sum(!is.na(.data$Value))*100, 1))
#PLOTTING PARAMETERS
legendFont <- list(
family = "sans-serif",
size = 14,
color = "#000")
#font for axis
axisFont <- list(
family = "sans-serif",
size = 17,
color = "#000")
#calculate trend line limits - confirm same fopr decdate as date...
min_x <- date_period[1]
max_x <- date_period[2]+1
min_y <- results$slope *min_x + results$int_from_data
max_y <- results$slope*max_x + results$int_from_data
#CREATE PLOT
p <- plot_ly(dataset, x = ~Date, y = ~plotVal,
color=I("#3182BD"),
type='scatter',
mode='lines',
showlegend=FALSE,
hoverinfo="none"
) %>%
add_markers(data=dataset %>% filter(plotCen == FALSE),
x = ~Date,y = ~plotVal,
color=I("#3182BD"),
symbol=I("circle"),
size=1,
showlegend=FALSE,
hoverinfo="text",
text= ~paste("Value:",plotVal,"<br>Date:",Date,"<br>Censored:",plotCen)
) %>%
add_markers(data=dataset %>% filter(plotCen == TRUE),
x = ~Date,y = ~plotVal,
size = 1,
symbol = I("circle-open"),
color=I("#3182BD"),
showlegend=TRUE,
name="Value too low to detect",
hoverinfo="text",
text=~paste("Value:",plotVal,"<br>Date:",Date,"<br>Censored:",plotCen)
)
#add trend line for significant trends only...
if(!any(results$slope_pval > alpha,results$slope == 0)){
p <- p %>%
add_segments(data=results,
x=min_x , xend=max_x, y=min_y , yend=max_y,
color="#FCBA19",
name="Long-term trend",
showlegend=TRUE,
inherit=TRUE)
}
return(p)
}
tmp <- plotly_1(dataset, results, date_period = c(2005,2014))
#glimpse(tmp)
tmp
The above does not show the markers in the legend, but
if I comment out the if(!any(results$slope_pval > alpha,results$slope == 0)) statement, the censored marker does show in the legend (as desired)
The problem is, that you are setting showlegend = FALSE in the plot_ly call, which has a global effect on the plot. If you add another add_lines instead of passing the data for the line trace directly in plot_ly you get the desired result:
library(plotly)
library(dplyr)
# datasets
alpha <- 0.6 # not defined in question
results <- structure(list(slope = 0, slope_p_val = 0.672652383888971, int_from_data = 0.06,
pct_cens = 9.3, annual_slope_units = 0, sigclass = "No evidence of trend",
sig = structure(2L, .Label = c("Significant", "Not significant"
), class = "factor"), slope_text = " ", trend_color = "#CBCBCB"), class = "data.frame", row.names = c(NA, -1L))
dataset <- structure(list(Date = structure(c(12794, 12823, 12863, 12893,
12921, 12948, 12978, 13003, 13048, 13073, 13108, 13137, 13172,
13199, 13230, 13263, 13291, 13318, 13349, 13375, 13405, 13432,
13472, 13486, 13523, 13564, 13592, 13622, 13648, 13683, 13705,
13746, 13775, 13810, 13838, 13852, 13929, 13957, 13986, 14014,
14053, 14067, 14110, 14139, 14166, 14196, 14224, 14266, 14294,
14321, 14348, 14377, 14405, 14446, 14476, 14501, 14532, 14566,
14593, 14636, 14684, 14712, 14740, 14770, 14811, 14839, 14868,
14896, 14929, 14952, 14993, 15020, 15050, 15077, 15105, 15146,
15174, 15208, 15238, 15265, 15293, 15315, 15350, 15385, 15412,
15441, 15482, 15511, 15537, 15566, 15600, 15631, 15658, 15685,
15728, 15742, 15769, 15811, 15839, 15868, 15904, 15931, 15958,
16001, 16030, 16042, 16091, 16119, 16149, 16174, 16204, 16230,
16268, 16302, 16330, 16359, 16386, 16412), class = "Date"),
cenTF = c(FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE),
Value = c(0.05, 0.06, 0.025, 0.08, 0.06, 0.07, 0.05, 0.025, 0.025, 0.1, 0.12,
0.18, 0.14, 0.19, 0.36, 0.17, 0.09, 0.07, 0.05, 0.025, 0.05,
0.025, 0.08, 0.05, 0.06, 0.06, 0.06, 0.05, 0.07, 0.06, 0.05,
0.07, 0.06, 0.05, 0.05, 0.06, 0.05, 0.07, 0.1, 0.09, 0.025, 0.07,
0.07, 0.14, 0.17, 0.11, 0.1, 0.14, 0.17, 0.17, 0.18, 0.09, 0.08,
0.08, 0.1, 0.07, 0.07, 0.06, 0.025, 0.09, 0.07, 0.08, 0.06, 0.06,
0.06, 0.08, 0.06, 0.06, 0.05, 0.05, 0.06, 0.06, 0.11, 0.1, 0.1,
0.05, 0.06, 0.025, 0.06, 0.025, 0.06, 0.06, 0.07, 0.06, 0.05,
0.07, 0.05, 0.06, 0.05, 0.05, 0.06, 0.06, 0.025, 0.05, 0.06,
0.06, 0.06, 0.23, 0.06, 0.06, 0.06, 0.025, 0.05, 0.05, 0.1, 0.06,
0.06, 0.06, 0.07, 0.08, 0.06, 0.07, 0.06, 0.05, 0.07, 0.06, 0.06,
0.06)), row.names = c(NA, -118L), class = "data.frame")
plotly_1 <- function(dataset,
results,
cen_var = "cenTF",
val_var = "Value",
date_period = NULL) {
dataset <- dataset %>%
mutate(
cenNM = case_when(
!!ensym(cen_var) := FALSE ~ " ",!!ensym(cen_var) := TRUE ~ "Value too low to detect"
),
plotVal = !!ensym(val_var),
plotCen = !!ensym(cen_var),
pct_cens = round(sum(.data$plotCen, na.rm = TRUE) / sum(!is.na(.data$Value)) *
100, 1)
)
#PLOTTING PARAMETERS
legendFont <- list(family = "sans-serif",
size = 14,
color = "#000")
#font for axis
axisFont <- list(family = "sans-serif",
size = 17,
color = "#000")
#calculate trend line limits - confirm same fopr decdate as date...
min_x <- date_period[1]
max_x <- date_period[2] + 1
min_y <- results$slope * min_x + results$int_from_data
max_y <- results$slope * max_x + results$int_from_data
#CREATE PLOT
p <- plot_ly(dataset,
type = 'scatter',
mode = 'lines',
hoverinfo = "none") %>%
add_lines(
x = ~ Date,
y = ~ plotVal,
color = I("#3182BD"),
showlegend = FALSE
) %>%
add_markers(
data = dataset %>% filter(plotCen == FALSE),
x = ~ Date,
y = ~ plotVal,
color = I("#3182BD"),
symbol = I("circle"),
size = 1,
showlegend = FALSE,
hoverinfo = "text",
text = ~ paste(
"Value:",
plotVal,
"<br>Date:",
Date,
"<br>Censored:",
plotCen
)
) %>%
add_markers(
data = dataset %>% filter(plotCen == TRUE),
x = ~ Date,
y = ~ plotVal,
size = 1,
symbol = I("circle-open"),
color = I("#3182BD"),
showlegend = TRUE,
name = "Value too low to detect",
hoverinfo = "text",
text = ~ paste(
"Value:",
plotVal,
"<br>Date:",
Date,
"<br>Censored:",
plotCen
)
)
# add trend line for significant trends only...
if (!any(results$slope_p_val > alpha, results$slope == 0)) {
p <- p %>%
add_segments(
data = results,
x = min_x ,
xend = max_x,
y = min_y ,
yend = max_y,
color = "#FCBA19",
name = "Long-term trend",
showlegend = TRUE,
inherit = TRUE
)
}
return(p)
}
tmp <- plotly_1(dataset, results, date_period = c(2005, 2014))
#glimpse(tmp)
tmp
Two more comments:
added alpha <- 0.6 as it wasn't defined.
Changed if(!any(results$slope_pval > alpha,results$slope == 0)){ to results$slope_p_val