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