eaebec39创建于 2024年10月24日历史提交
package scientific.matplot

import std.unittest.*
import std.unittest.testmacro.*

import scientific.numbers.*
import scientific.linear.*
import scientific.stats.random.*
import scientific.stats.normal.*

/* Type for x: Float64 */
foreign func c_boxplot(x: CPointer<Unit>, x_len: Int64): CPointer<Unit>
foreign func c_boxplot_2d(x: CPointer<Unit>, row: Int64, col: Int64): CPointer<Unit>
foreign func c_boxplot_str(x: CPointer<Unit>, groups: CPointer<CString>, len: Int64): CPointer<Unit>
foreign func c_box_style(box: CPointer<Unit>, style: CString): CPointer<Unit>

public class BoxChart {
    var ptr: CPointer<Unit> = CPointer<Unit>()

    init(ptr: CPointer<Unit>) {
        this.ptr = ptr
    }

    public func box_style(style: BoxStyleOption) {
        var cstr_style = unsafe { LibC.mallocCString(style.toString()) }
        this.ptr = unsafe { c_box_style(this.ptr, cstr_style) }
        unsafe { LibC.free(cstr_style) }
        return this
    }
}

public enum BoxStyleOption {
    | outline   // unfilled / dashed whiskers
    | filled    // filled / solid whiskers
    | violin    // show data distribution

    func toString(): String {
        return match (this) {
            case outline => "outline"
            case filled => "filled"
            case violin => "violin"
        }
    }
}

public func boxplot(x: Vector<Float64>) {
    let size = x.size()
    let handle = unsafe { c_boxplot(x.ptr, size) }
    return BoxChart(handle)
}

public func boxplot(x: Matrix<Float64>) {
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_boxplot_2d(x.ptr, row, col) }
    return BoxChart(handle)
}

public func boxplot(x: Vector<Float64>, groups: Array<String>) {
    if (x.size() != groups.size) {
        throw IllegalArgumentException("boxplot: size of data and groups does not match")
    }

    var a = unsafe { malloc(UIntNative(8 * x.size())) }
    var group_ptr = CPointer<CString>(a)
    for (i in 0..x.size()) {
        var cstr_group = unsafe { LibC.mallocCString(groups[i]) }
        unsafe { group_ptr.write(i, cstr_group) }
        unsafe { LibC.free(cstr_group) }
    }
    let handle = unsafe { c_boxplot_str(x.ptr, group_ptr, x.size() ) }
    return BoxChart(handle)
}

public func testBox1() {
    print("    - testBox1\n")
    let mpg = vector<Float64>([
        18.0, 15.0, 18.0, 16.0, 17.0, 15.0, 14.0, 14.0, 14.0, 15.0, 15.0, 14.0,
        15.0, 14.0, 24.0, 22.0, 18.0, 21.0, 27.0, 26.0, 25.0, 24.0, 25.0, 26.0,
        21.0, 10.0, 10.0, 11.0,  9.0, 28.0, 25.0, 25.0, 26.0, 27.0, 17.5, 16.0,
        15.5, 14.5, 22.0, 22.0, 24.0, 22.5, 29.0, 24.5, 29.0, 33.0, 20.0, 18.0,
        18.5, 17.5, 29.5, 32.0, 28.0, 26.5, 20.0, 13.0, 19.0, 19.0, 16.5, 16.5,
        13.0, 13.0, 13.0, 28.0, 27.0, 34.0, 31.0, 29.0, 27.0, 24.0, 23.0, 36.0,
        37.0, 31.0, 38.0, 36.0, 36.0, 36.0, 34.0, 38.0, 32.0, 38.0, 25.0, 38.0,
        26.0, 22.0, 32.0, 36.0, 27.0, 27.0, 44.0, 32.0, 28.0, 31.0])
    boxplot(mpg)
    xlabel("All Vehicles")
    ylabel("Miles per Gallon (MPG)")
    title("Miles per Gallon for All Vehicles")

    save("./tests/imgs/boxplot/boxchart_1.svg", "svg")
    clear()
}

public func testBox2() {
    print("    - testBox2\n")
    let mpg = vector<Float64>([
        18.0, 15.0, 18.0, 16.0, 17.0, 15.0, 14.0, 14.0, 14.0, 15.0, 15.0, 14.0,
        15.0, 14.0, 24.0, 22.0, 18.0, 21.0, 27.0, 26.0, 25.0, 24.0, 25.0, 26.0,
        21.0, 10.0, 10.0, 11.0,  9.0, 28.0, 25.0, 25.0, 26.0, 27.0, 17.5, 16.0,
        15.5, 14.5, 22.0, 22.0, 24.0, 22.5, 29.0, 24.5, 29.0, 33.0, 20.0, 18.0,
        18.5, 17.5, 29.5, 32.0, 28.0, 26.5, 20.0, 13.0, 19.0, 19.0, 16.5, 16.5,
        13.0, 13.0, 13.0, 28.0, 27.0, 34.0, 31.0, 29.0, 27.0, 24.0, 23.0, 36.0,
        37.0, 31.0, 38.0, 36.0, 36.0, 36.0, 34.0, 38.0, 32.0, 38.0, 25.0, 38.0,
        26.0, 22.0, 32.0, 36.0, 27.0, 27.0, 44.0, 32.0, 28.0, 31.0])

    let origin = [
        "USA",     "USA",     "USA",     "USA",   "USA",   "USA",     "USA",
        "USA",     "USA",     "USA",     "USA",   "USA",   "USA",     "USA",
        "Japan",   "USA",     "USA",     "USA",   "Japan", "Germany", "France",
        "Germany", "Sweden",  "Germany", "USA",   "USA",   "USA",     "USA",
        "USA",     "Italy",   "Germany", "USA",   "USA",   "France",  "USA",
        "USA",     "USA",     "USA",     "USA",   "USA",   "USA",     "USA",
        "USA",     "USA",     "Germany", "Japan", "USA",   "USA",     "USA",
        "USA",     "Germany", "Japan",   "Japan", "USA",   "Sweden",  "USA",
        "France",  "Japan",   "Germany", "USA",   "USA",   "USA",     "USA",
        "USA",     "USA",     "USA",     "USA",   "USA",   "USA",     "USA",
        "USA",     "Germany", "Japan",   "Japan", "USA",   "USA",     "Japan",
        "Japan",   "Japan",   "Japan",   "Japan", "Japan", "USA",     "USA",
        "USA",     "USA",     "Japan",   "USA",   "USA",   "USA",     "Germany",
        "USA",     "USA",     "USA"
    ]

    boxplot(mpg, origin)
    xlabel("All Vehicles")
    ylabel("Miles per Gallon (MPG)")
    title("Miles per Gallon for All Vehicles")

    save("./tests/imgs/boxplot/boxchart_2.svg", "svg")
    clear()
}

public func testBox3() {
    print("    - testBox3\n")
    var m: Random = Random(0)
    let x = randn(m, 25, 100, 0.0, 1.0)

    subplot(2,1,0)
    boxplot(x)

    subplot(2,1,1)
    let box = boxplot(x)
    box.box_style(outline)

    save("./tests/imgs/boxplot/boxchart_3.svg", "svg")
    clear()
}

public func testBoxChart() {
    print("  + testBoxChart\n")
    testBox1()
    testBox2()
    testBox3()
}