雜篇:Android繪製函式圖象及正弦函式的介紹

張風捷特烈發表於2019-01-02

零、前言

這篇是為了下一篇做點鋪墊,也是來複習一些數學基礎
本篇屬於休閒娛樂,不要太較真,小科普一下,不喜勿噴
本文知識點前4點你可以隨便看看,但第5點非常重要,本文原始碼見捷文規範

本文知識點:
1)數學函式的概念
2)直角座標系的下函式圖形
3)極座標下的函式圖象
4)引數方程下的函式圖形
5)正弦函式的詳細分析(為下一篇文章做鋪墊)


一、數學函式的概念:

1.高中數學必修1:
設A,B為非空的數集,如果按照某種確定的對應關係f,  
使對於集合A中的任意的任意一個數x,在集合B中都有唯一確定的數f(x)和它對應,
那麼就稱"f:A→B"為從集合A到集合B的一個函式,記作:
y=f(x),x∈A

其中,x叫做自變數,x的取值範圍叫做函式的[定義域]
與x的值對應的y值叫做函式值,函式值的集合{f(x)|x∈A}叫做函式的[值域]
複製程式碼

2.大學高等數學
設數集D⊂ R,則稱對映f:D→R為定義在D上的函式,通常簡記為
y=f(x),x∈ D

其中x稱自變數,y稱因變數,D稱定義域,記作Df,即Df=D.
值域:Rf=f(D)={y|y=f(x),x∈ D}
複製程式碼

3.對映:
設X,Y是兩個非集合,如果存在一個法則f,使的對X中的每個元素x,
按法則f,在Y中有唯一確定的元素y與之對應,則稱f為X到Y的對映,記作
f:X→Y

其中y稱為元素x(在對映f下)的像,並記作f(x),即y=f(x)
而元素x稱為元素y(在對映f下)的原像
複製程式碼

二、直角座標系的下函式圖形

這裡只是模擬函式,然後繪製出可視的圖象
數學中的實數是連續的,這裡在螢幕中將畫素作為基本的單元
繪圖核心:點整合線,單點半徑1px
自變數:x
定義域:Df用集合Set表示
函式關係:函式f(x)
點集用Map表示,x→y


0.網格與座標系的繪製

網格和座標系我已經封裝,初始View如下:

public class MathView extends View {
    private Point mCoo = new Point(500, 700);//座標系
    private Picture mCooPicture;//座標系canvas元件
    private Picture mGridPicture;//網格canvas元件
    private Paint mHelpPint;//輔助畫筆
    private Paint mPaint;//主畫筆
    private Path mPath;//主路徑
    public MathView(Context context) {
        this(context, null);
    }

    public MathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();//初始化
    }

    private void init() {
        //初始化主畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        //初始化主路徑
        mPath = new Path();

        //初始化輔助
        mHelpPint = HelpDraw.getHelpPint(Color.RED);
        mCooPicture = HelpDraw.getCoo(getContext(), mCoo);
        mGridPicture = HelpDraw.getGrid(getContext());
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        HelpDraw.draw(canvas, mGridPicture, mCooPicture);
        canvas.save();
        canvas.translate(mCoo.x, mCoo.y);
        canvas.scale(1, -1);//y軸向上
        canvas.restore();
    }

複製程式碼

網格和座標系準備.png

具體細節這裡不說了,詳見:Android關於Canvas你所知道的和不知道的一切,或原始碼


1.一次函式:y=x,定義域[-200,300]

y=x.png


1.1:幾個成員變數
private TreeSet<Float> Df = new TreeSet<>();//定義域
private Map<Float, Float> funMap = new HashMap<>();//對映表
private Paint mTextPaint;//文字畫筆
複製程式碼

1.2:初始化定義域
/**
 * 初始化定義域
 */
private void initDf() {
    for (float i = -200; i <= 300; i++) {
        Df.add(i);//初始化定義域
    }
}
複製程式碼

1.3:對應法則fx
/**
 * 對應法則
 * @param x 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float x) {
    float y = x;
    return y;
}
複製程式碼

1.4:遍歷定義域,將原像x和像f(x)加入對映表
/**
 * 遍歷定義域,將原像x和像f(x)加入對映表
 */
private void map() {
    Df.forEach(x -> {
        funMap.put(x, f(x));
    });
    //新增所有點
}
複製程式碼

1.5:繪製對映表
/**
 * 繪製對映表
 * @param canvas 畫筆
 * @param map 點集對映表
 */
private void drawMap(Canvas canvas, Map<Float, Float> map) {
    map.forEach((k, v) -> {
        canvas.drawPoint(k, v, mPaint);
    });
}
複製程式碼

2.絕對值函式:y=|x|,定義域[-200,300]

只需改一點

y=abs(x).png

/**
 * 對應法則
 * @param x 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float x) {
    float y=Math.abs(x);
    return y;
}
複製程式碼

3.二次函式,定義域[-200,300]

二次函式.png

/**
 * 對應法則
 * @param x 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float x) {
    float y=(x - 100) * (x - 100) / 200 + 100;
    return y;
}
複製程式碼

4.對數函式:log10(x)為例,定義域[1,1000]

log10.png

/**
 * 初始化定義域
 */
private void initDf() {
    for (float i = 1; i <= 1000; i++) {
        Df.add(i);//初始化定義域
    }
}

/**
 * 對應法則
 *
 * @param x 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float x) {
    float y = (float) (100.f * Math.log10(x));
    return y;
}
複製程式碼

5.指數函式:定義域[-400,500]

指數函式.png

/**
 * 初始化定義域
 */
private void initDf() {
    for (float i = -400; i <= 500; i++) {
        Df.add(i);//初始化定義域
    }
}

/**
 * 對應法則
 *
 * @param x 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float x) {
    float y= 100*(float) Math.pow(Math.E,x/300f);
    return y;
}
複製程式碼

6.正弦函式:定義域[-360°,450°]

正弦函式.png

/**
 * 初始化定義域
 */
private void initDf() {
    for (float i =-360; i <= 450; i++) {
        Df.add(i);//初始化定義域
    }
}

/**
 * 對應法則
 *
 * @param x 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float x) {
    float y= (float) (100*Math.sin(Math.PI/180*x));
    return y;
}
複製程式碼

經歷過上面幾個函式的繪製,不難發現,只有更改對應法則,即函式關係式就可以了


三、極座標下的函式圖象

1).尋找角度thta和長度p的函式關係
2).使用極座標與直角座標系的轉換關係來繪製點集


1.笛卡爾心型線:ρ= 100*(1-cosθ)

極座標方程--笛卡爾心型線.png

/**
 * 初始化定義域
 */
private void initDf() {
    for (float i = 1; i <= 360; i++) {
        Df.add(i);//初始化定義域
    }
}

/**
 * 繪製對映表
 *
 * @param canvas 畫筆
 * @param map    點集對映表
 */
private void drawMap(Canvas canvas, Map<Float, Float> map) {
    map.forEach((thta, p) -> {
        Log.e(TAG, "drawMap: "+p+thta);
        canvas.drawPoint((float) (p * Math.cos(thta)), (float) (p * Math.sin(thta)), mPaint);
    });
}

/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float thta) {
    float p = (float) (100 * (1 - Math.cos(thta)));
    return p;
}

/**
 * 遍歷定義域,將原像x和像f(x)加入對映表
 */
private void map() {
    Df.forEach(x -> {
        float thta = (float) (Math.PI / 180 * x);
        funMap.put(thta, f(thta));
    });
    //新增所有點
}
複製程式碼

2.四葉草:ρ= 100*(1-4*sinθ)

極座標方程--四葉草.png

/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float thta) {
    float p = (float) (100 * (1 - Math.sin(4 * thta)));
    return p;
}
複製程式碼

3.畫著玩:ρ=(e^(cosθ)- 2cos(4θ) + [sin(θ/12)]^5)*100

極座標方程--畫著玩.png

/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float thta) {
    float p = (float) (100f*(Math.pow(Math.E,Math.cos(thta)) - 2 * Math.cos(4 * thta) + Math.pow(Math.sin(thta / 12), 5)));;
    return p;
}
複製程式碼

4.渦旋線:ρ= a*θ

渦旋線.png

/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float thta) {
    float p = 30*thta;
    return p;
}
複製程式碼

5.極座標下的圓

極座標下的圓.png

/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return 像(因變數)
 */
private float f(Float thta) {
    float p = 200;
    return p;
}

複製程式碼

四、引數方程下的函式圖象

1.雙曲線:x=a/cosα,y=btanα

引數方程模擬雙曲線.png

    /**
     * 初始化定義域
     */
    private void initDf() {
        for (float i = 0; i <= 360 ; i++) {
            Df.add(i);//初始化定義域
        }
    }

    /**
     * 繪製對映表
     *
     * @param canvas 畫筆
     * @param map    點集對映表
     */
    private void drawMap(Canvas canvas, Map<Float, Float> map) {
        map.forEach((k, v) -> {
            canvas.drawPoint(k, v, mPaint);
        });
    }

    /**
     * 對應法則
     *
     * @param thta 原像(自變數)
     * @return y像(因變數)
     */
    private float y(Float thta) {
        float y = (float) (100 * Math.tan(thta));
        return y;
    }

    /**
     * 對應法則
     *
     * @param thta 原像(自變數)
     * @return x像(因變數)
     */
    private float x(Float thta) {
        float x = (float) (200 / Math.cos(thta));
        return x;
    }

    /**
     * 遍歷定義域,將原像x和像f(x)加入對映表
     */
    private void map() {
        Df.forEach(x -> {
            float thta = (float) (Math.PI / 180 * x);
            funMap.put(x(thta), y(thta));
        });
        //新增所有點
    }
複製程式碼

2.橢圓:x=a*cosα,y=bsinα

橢圓.png

/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return y像(因變數)
 */
private float y(Float thta) {
    float y = (float) (300 * Math.sin(thta));
    return y;
}
/**
 * 對應法則
 *
 * @param thta 原像(自變數)
 * @return x像(因變數)
 */
private float x(Float thta) {
    float x = (float) (400 * Math.cos(thta));
    return x;
}
複製程式碼

3.引數方程:雙鈕線x=a√(cos2θ )cosθ,y=a√(cos2θ)sinθ

雙鈕線.png

/**
 * 對應法則:y=a√(cos2θ)sinθ
 *
 * @param thta 原像(自變數)
 * @return y像(因變數)
 */
private float y(Float thta) {
    float y = (float) (200 * Math.sqrt(Math.cos(2*thta))*Math.sin(thta));
    return y;
}

/**
 * 對應法則:x=a√(cos2θ )cosθ
 *
 * @param thta 原像(自變數)
 * @return x像(因變數)
 */
private float x(Float thta) {
    float x = (float) (200 * Math.sqrt(Math.cos(2*thta))*Math.cos(thta));
    return x;
}
複製程式碼

五、分析與優化

1.分析

你可能已經吐槽了:什麼鬼,怎麼後面都是斷斷續續的點拼成的
等等...先別急,我們來看看這幅圖能說明什麼?
先看一下定義域: [-360,450],共810個點,每個點半徑1px,每個點橫向距離1px
點密集則說明相鄰兩點間的dy很小,相反,稀疏則說明相鄰兩點間的dy很大
也就是密集說明函式變化的幅度小,稀疏說明函式變化的幅度大
當相鄰兩點距離大於圓的直徑(2px)時,視覺上會看出兩個點,即不連續。

斷續的點問題.png


2.分析總結

為了方便描述,這裡定義了幾個概念

如果把一條完美的函式曲線看作P,
那所有現實中(紙、螢幕)的函式圖象P'都是對P的取點模擬,
從P上取點的行為稱為[取樣],
取樣的個數稱為[取樣總數],
取樣的相鄰兩點xn,xn+1間的距離稱為[取樣距離dxn]
當每個dxn值都相等的時,稱為[等距取樣]
兩個樣本點pn,pn+1之間的距離稱為[樣本距離dpn]
複製程式碼

3.看一下連續的點有哪些

在加入點集時過濾掉相鄰兩點間距離大於直徑的點

/**
 * 兩點間的距離
 * @return
 */
private float dis(float x0, float y0, float x1, float y1) {
    return (float) Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1));
}
複製程式碼
/**
 * 遍歷定義域,將原像x和像f(x)加入對映表
 */
private void map() {
    Df.forEach(x -> {
        float dis = dis(x, f(x), x + 1, f(x + 1));//每相鄰兩點間距離
        if (dis < mLineWidth && dis > mLineWidth / 2) {
            funMap.put(x, f(x));
        }
    });
複製程式碼

連續點.png


4.不行連續的點處理思路:

思路也就是在間距處再取樣

處理

/**
 * 遍歷定義域,將原像x和像f(x)加入對映表
 */
private void map() {
    Df.forEach(x -> {
        float dis = dis(x, f(x), x + 1, f(x + 1));//每相鄰兩點間距離
        if (dis < mLineWidth && dis > mLineWidth / 2) {
            funMap.put(x, f(x));
        } else if (dis > mLineWidth) {
            float num = dis / mLineWidth;//在切割數
            for (float di = 0; di <= num; di += (1.f / num)) {
                x += di;
                funMap.put(x, f(x));
            }
        }
    });
    //新增所有點
}
複製程式碼

優化後.png


六、正弦函式的詳細分析

1.正弦函式簡介

正弦函式表示式.png

其中A,ω,φ,k是常數,且ω≠0
振幅:A
角頻率:ω
週期:T=2π/ω 
頻率:f=1/T=ω/2π
相位:ωx+φ
初相:φ
平衡線:y=k
波峰:最大值|A|
波谷:最小值-|A|
複製程式碼

2.振幅A:離開平衡位置的最大距離

下面橫軸的每格代表90°,化為弧度製表示即:π/2,每四格是360°,即2π

2.1:A=300

A=300.png


2.2:A=100

A=100.png


2.3:振幅的作用
決定正弦曲線的波峰與波谷,形象來說就是"高矮" 
振幅越大,波峰越高,波谷越低,每個週期的圖象顯得"高"
複製程式碼

3.角頻率ω:單位時間內變化的相角弧度值
3.1:ω=2

ω=2.png


3.2:ω=5

ω=5.png


3.3:角頻率的作用
決定正弦曲線的週期,形象來說就是"胖瘦" 
角頻率越大,週期越小,每個週期的圖象顯得"瘦"

ω=2 週期:T = 2π/ω = π 從圖中看,每兩格一週期,即π 
頻率:f = 1/T = 1/π
複製程式碼

4.初相φ:x=0時的相位
4.1:φ=π/6

φ=π/6


4.2:φ=π/2

φ=π/2


4.3:振幅的作用
相位決定了標準正弦函式的左右偏移:正左偏,負右偏,偏移量:φ/ω
複製程式碼

5.平衡值k:決定平衡線的位置
5.1:k=100

k=100.png


5.2:k=200

k=200.png

5.3:平衡值的作用
平衡值決定標準正弦函式的上下偏移:正上偏,負下偏,偏移量:k
複製程式碼

現在對於幾個正弦函式的引數值已經有了一點了解,本篇結束


附錄:一些常用符號:
θ ρ φ
π α β γ
η μ ζ Ω

後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1-github 2018-1-2 Android繪製函式圖象及正弦函式的介紹
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章