構造体配列による信号波形を描画する
1次元信号処理のプログラミングをする場合、x と f(x) のように、横軸の値と縦軸の値をワンセットにした構造体を定義して、それを一つの配列要素としてデータ列として扱う場合が定番です。
本記事では、このような信号データの波形を画面に表示する方法を紹介します。具体的には、下記のような3種類の波形を画面に表示します。
いきなりコード解説をします。
3〜6行目、構造体の定義です。1次元信号において x が横軸のデータです。fx が縦軸のデータです。
10〜11行目、データの個数を定義します。きめ細かいデータを表示したい場合 NUM_DATA を大きくとってください。ためしに 1000 を 100 にしてみると効き目がわかると思います。
125行目、シンク関数をセットするメソッドです。
161行目、サイン関数をセットするメソッドです。
186行目、コサイン関数をセットするメソッドです。
120行目、起動時はシンク関数をセットします。
36〜54行目、波形を描画している部分です。
40、49行目、描画座標系は左上隅が(0, 0)なので信号データにマイナスをかけています。
46、47、49行目、波形描画の連続性を優先して描画座標から実データの要素インデックスを逆算します。
58、78行目、Path の描画範囲を決定する .frame() の値は、背景の Rectangle() と同じサイズにします。
import SwiftUI
struct X_FX {
var x: Double
var fx: Double
}
struct ContentView: View {
let NUM_DATA = 1000
@State var Data: [X_FX] = []
var body: some View {
ZStack{
// 全体背景の矩形を描画する.
Rectangle()
.foregroundColor( Color.mint )
// 波形描画の矩形サイズ.
let RECT_W = 300.0
let RECT_H = 400.0
// 波形描画の背景の矩形を描画する.
Rectangle()
.foregroundColor( Color.black )
.frame( width: RECT_W, height: RECT_H )
// 波形を描画する.
Path{ path in
let amp = RECT_H * 0.5
let off_y = RECT_H * 0.5
if ( Data.count > 0 )
{
var plot_x = Double(0)
var plot_y = -( Data[0].fx ) * amp + off_y
var pnt = CGPoint( x: plot_x, y: plot_y )
path.move( to: pnt )
let loop = Int( RECT_W )
for i in 1 ..< loop {
let rate = Double(i)/Double( RECT_W - 1 )
let n = Int( rate * Double( Data.count - 1 ))
plot_x = Double(i)
plot_y = -( Data[n].fx ) * amp + off_y
pnt = CGPoint( x: plot_x, y: plot_y )
path.addLine( to: pnt )
}
}
}
.stroke( .green, lineWidth: 2.0 )
.frame( width: RECT_W, height: RECT_H ) // ここが重要.
// 目安になるセンター十字線を描画する.
Path{ path in
let plot_x = RECT_W * 0.5
let plot_y = RECT_H * 0.5
let pnt00 = CGPoint( x: plot_x, y: 0 )
let pnt01 = CGPoint( x: plot_x, y: RECT_H )
path.move( to: pnt00 )
path.addLine( to: pnt01 )
let pnt10 = CGPoint( x: 0, y: plot_y )
let pnt11 = CGPoint( x: RECT_W, y: plot_y )
path.move( to: pnt10 )
path.addLine( to: pnt11 )
}
.stroke( .white, lineWidth: 1.0 )
.frame( width: RECT_W, height: RECT_H ) // ここが重要.
// データをセットするボタンを水平に並べる.
HStack{
// シンク関数をセットするボタン.
Button( action: {
let _ = SetDataSinc( &Data, NUM_DATA )
}){
Text( "sinc" )
.foregroundColor( .white )
}
.padding()
.background( .blue )
// サイン関数をセットするボタン.
Button( action: {
let _ = SetDataSin( &Data, NUM_DATA )
}){
Text( "sin" )
.foregroundColor( .white )
}
.padding()
.background( .blue )
// コサイン関数データをセットするボタン.
Button( action: {
let _ = SetDataCos( &Data, NUM_DATA )
}){
Text( "cos" )
.foregroundColor( .white )
}
.padding()
.background( .blue )
}
.frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom )
.padding( .bottom )
}
.onAppear(){
// 起動時にはシンク関数を描画する.
SetDataSinc( &Data, NUM_DATA )
}
}
func SetDataSinc( _ data: inout [X_FX], _ req_num_data: Int )->Void {
// いったん配列をクリアする.
data.removeAll()
let RANGE = 2.0
let MUL = 2.0
for n in 0 ..< req_num_data {
let rate = Double(n)/Double( req_num_data - 1)
var v = RANGE * rate
v -= 1.0 // この時点で -1.0 〜 0.0 〜 +1.0 になる.
let t = v * ( 2.0 * ( Double.pi )) * MUL
var ft = 0.0
// ゼロ割チェック.
if ( t != 0.0 )
{
ft = sin( t ) / t
}
else
{
ft = 1.0
}
// データ要素を追加する.
let element = X_FX( x: t, fx: ft )
data.append( element )
}
}
func SetDataSin( _ data: inout [X_FX], _ req_num_data: Int )->Void {
// いったん配列をクリアする.
data.removeAll()
let RANGE = 2.0
let MUL = 2.0
for n in 0 ..< req_num_data {
let rate = Double(n)/Double( req_num_data - 1)
var v = RANGE * rate
v -= 1.0 // この時点で -1.0 〜 0.0 〜 +1.0 になる.
let t = v * ( 2.0 * ( Double.pi )) * MUL
let ft = sin( t )
// データ要素を追加する.
let element = X_FX( x: t, fx: ft )
data.append( element )
}
}
func SetDataCos( _ data: inout [X_FX], _ req_num_data: Int )->Void {
// いったん配列をクリアする.
data.removeAll()
let RANGE = 2.0
let MUL = 2.0
for n in 0 ..< req_num_data {
let rate = Double(n)/Double( req_num_data - 1)
var v = RANGE * rate
v -= 1.0 // この時点で -1.0 〜 0.0 〜 +1.0 になる.
let t = v * ( 2.0 * ( Double.pi )) * MUL
let ft = cos( t )
// データ要素を追加する.
let element = X_FX( x: t, fx: ft )
data.append( element )
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}