멀티 그래프 도화 라인 라이브러리를 디자인

저자:작은 꿈, 창작: 2022-03-31 11:28:35, 업데이트: 2023-09-20 10:04:46

img

그래프 도면을 만드는 것과 같은 작업은 종종 작성과 디자인 정책에서 사용됩니다. 단일 사용자 정의 그래프에 대해 우리는"그림자리 클래식 라이브러리"(FMZ에 있는 템플릿 클래스 라이브러리 개념에 익숙하지 않은 학생들은 FMZ API 문서를 참조할 수 있다.) 그림과 관련된 작업은 매우 편리하다. 그러나 여러 개의 그래프가 필요한 시나리오에서는 템플릿 클래스가 요구사항을 충족시킬 수 없다. 그래서 우리는 이 그림줄 클래스 라이브러리의 디자인 아이디어를 배우고, 이를 바탕으로 다차트 버전의 그림줄 라이브러리 클래스를 설계한다.

템플릿 클래스 라이브러리의 출력 함수를 설계합니다.

"그림줄 클래스 라이브러리"의 출력 함수 디자인을 바탕으로, 다차례의 그림줄 클래스 라이브러리에도 비슷한 출력 함수를 설계했습니다.

  • $.PlotMultRecords K선 그래프를 그리는 데 사용되는 파라미터 디자인:cfgName, seriesName, records, extension. cfgName: 독립적인 그래프로 구성된 객체의 이름. seriesName: 현재 도면을 그리기 위한 K선 데이터 시리즈의 이름. records: K선 데이터를 전송합니다. extension: 그래프 크기의 설정 정보, 예를 들어:{layout: 'single', col: 6, height: '600px'}즉, cfgName라는 이름의 구성 객체를 가진 그래프가 각각, 너비 6, 높이 600px로 표시된다.

  • $.plotMultLine 그림줄, 매개 변수 디자인:cfgName, seriesName, dot, ts, extension cfgName: 독립적인 그래프로 구성된 객체의 이름. seriesName: 현재 그리는 줄의 데이터 시리즈의 이름. dot: 그리기 원하는 선의 지점의 수직 좌표 값. ts: 시간표, 즉 x 시간축의 값. extension: 그래프 크기의 설정 정보.

  • $.PlotMultHLine 수평선을 그리는 데 사용되며, 파라미터 설계:cfgName, value, label, color, style cfgName: 그래프 구성 객체의 이름. value: 수평선의 수직 좌표 값. label: 수평선 위에 텍스트를 표시합니다. 색: 줄의 색. style: 문자열의 스타일, 예를 들어:Solid ShortDash ShortDot ShortDashDot ShortDashDotDot Dot Dash LongDash DashDot LongDashDot LongDashDotDot

  • $.PlotMultTitle 그래프의 제목, 부 제목을 수정하기 위해 사용된다. 파라그램 디자인:cfgName, title, chartTitle cfgName: 그래프 구성 객체의 이름. title: 부설 제목. chartTitle: 차트 제목.

  • $.plotMultFlag 그림 플래그 작은 아이콘, 파라미터 디자인:cfgName, seriesName, ts, text, title, shape, color, onSeriesName cfgName: 그래프 구성 객체의 이름. seriesName: 데이터 시리즈 이름. ts: 시간표 text: 작은 아이콘의 텍스트. title: 작은 아이콘의 제목. shape: 작은 아이콘 모양. 색상: 작은 아이콘 색상. onSeriesName: 어떤 데이터 계열에 따라 표시되는 지에 따라 데이터 계열의 id가 됩니다.

  • $.GetArrCfg 그래프 구성 객체 배열을 반환합니다.

테스트 함수 설계

이해하기 쉽게, 나는 테스트 함수 위에 직접 논문을 작성하여 각 함수의 호출의 역할을 설명합니다.

// test
function main() {
    LogReset(10)
    var i = 0 
    var prePrintTs = 0

    while (true) {
        var r = exchange.GetRecords()   // 获取K线数据
        var t = exchange.GetTicker()    // 获取实时的tick数据

        $.PlotMultRecords("chart1", "kline1", r, {layout: 'single', col: 6, height: '600px'})   // 创建一个名为chart1的K线图表,独立显示,宽度是6,高度是600px,K线数据系列名称为kline1,使用上面获取的r作为数据源画图
        $.PlotMultRecords("chart2", "kline2", r, {layout: 'single', col: 6, height: '600px'})   // 创建第二个K线图表,名为chart2
        $.PlotMultLine("chart2", "line1", t.Last, r[r.length - 1].Time)  // 在K线图表即chart2上增加一条线,数据系列名称为line1,使用当前的tick数据的最新价Last作为线上的点的Y值。K线数据的最后一个BAR的时间戳作为X值
        $.PlotMultLine("chart3", "line2", t.Last)   // 创建一个只画线的图表,图表名称chart3,数据系列名称line2,使用实时tick数据的Last最新价格在当前时间(X值)画一个点(t.Last为Y值),注意图表不是独立显示
        $.PlotMultLine("chart6", "line6", t.Time)   // 创建一个只画线的图表chart6,注意图表不是独立显示,会和chart3在一起分页显示
        $.PlotMultLine("chart4", "line3", t.Sell, new Date().getTime(), {layout: 'single', col: 4, height: '300px'})  // 创建一个只画线的图表chart4,独立显示,宽度4,高度300px
        $.PlotMultLine("chart5", "line4", t.Volume, new Date().getTime(), {layout: 'single', col: 8, height: '300px'})  // 创建一个只画线的图表chart5,独立显示,宽度8,高度300px     

        $.PlotMultHLine("chart1", r[r.length - 1].Close, "HLine1", "blue", "ShortDot")   // 给图表chart1增加水平横线
        $.PlotMultHLine("chart4", t.Sell, "HLine2", "green")  // 给图表chart4增加水平横线
        $.PlotMultTitle("chart3", "change : chart3->test1", "test1")   // 修改chart3的标题

        var ts = new Date().getTime()
        if (ts - prePrintTs > 1000 * 20) {
            prePrintTs = ts 
            // 触发时,给chart3图表上画小图标
            $.PlotMultFlag("chart3", "flag1", new Date().getTime(), "flag test", "flag1")
        }
        
        if (i == 10) {
            Log("i == 10")
            // 触发时,给chart4,chart1上画小图标
            $.PlotMultFlag("chart4", "flag2", new Date().getTime(), "flag test", "flag2")
            $.PlotMultFlag("chart1", "flag3", new Date().getTime(), "flag test", "flag3", "squarepin", "green", "kline1")
        } else if (i == 20) {
            Log("i == 20")
            // 触发时,给chart1上添加一条线,但是只画了这条线的一个点,X坐标时间戳,Y坐标为t.Last值
            $.PlotMultLine("chart1", "line5", t.Last, r[r.length - 1].Time)
        } else if (i == 30) {
            Log("i == 30")
            // 触发时,给chart2上画小图标
            $.PlotMultFlag("chart2", "flag4", new Date().getTime(), "flag test", "flag4", "circlepin", "black", "kline2")
        }
        
        Sleep(1000 * 5)
        i++
    }
}

실행 테스트

img

img

한 줄의 함수 호출을 통해 하나의 그래프를 쉽게 그리는 것을 볼 수 있고, 동시에 여러 개의 그래프를 표시할 수 있습니다.

전체 전략 소스 코드

패러미터 설정:img

클래스 라이브러리 소스 구현:


var registerInfo = {}
var chart = null
var arrCfg = []

function updateSeriesIdx() {
    var index = 0
    var map = {}
    _.each(arrCfg, function(cfg) {
        _.each(cfg.series, function(series) {
            var key = cfg.name + "|" + series.name
            map[key] = index
            index++
        })
    })

    for (var cfgName in registerInfo) {
        _.each(arrCfg, function(cfg, cfgIdx) {
            if (cfg.name == cfgName) {
                registerInfo[cfgName].cfgIdx = cfgIdx
            }
        })

        for (var i in registerInfo[cfgName].seriesIdxs) {
            var seriesName = registerInfo[cfgName].seriesIdxs[i].seriesName
            var key = cfgName + "|" + seriesName
            if (typeof(map[key]) != "undefined") {                
                registerInfo[cfgName].seriesIdxs[i].index = map[key]
            }

            if (registerInfo[cfgName].seriesIdxs[i].type == "candlestick") {
                registerInfo[cfgName].seriesIdxs[i].preBarTime = 0
            } else if (registerInfo[cfgName].seriesIdxs[i].type == "line") {
                registerInfo[cfgName].seriesIdxs[i].preDotTime = 0
            } else if (registerInfo[cfgName].seriesIdxs[i].type == "flag") {
                registerInfo[cfgName].seriesIdxs[i].preFlagTime = 0
            }
        }
    }

    if (!chart) {
        chart = Chart(arrCfg)
    }
    chart.update(arrCfg)
    chart.reset()

    _G("registerInfo", registerInfo)
    _G("arrCfg", arrCfg)
    
    for (var cfgName in registerInfo) {
        for (var i in registerInfo[cfgName].seriesIdxs) {
            var buffer = registerInfo[cfgName].seriesIdxs[i].buffer
            var index = registerInfo[cfgName].seriesIdxs[i].index
            if (buffer && buffer.length != 0 && registerInfo[cfgName].seriesIdxs[i].type == "line" && registerInfo[cfgName].seriesIdxs[i].preDotTime == 0) {
                _.each(buffer, function(obj) {
                    chart.add(index, [obj.ts, obj.dot])
                    registerInfo[cfgName].seriesIdxs[i].preDotTime = obj.ts
                })
            } else if (buffer && buffer.length != 0 && registerInfo[cfgName].seriesIdxs[i].type == "flag" && registerInfo[cfgName].seriesIdxs[i].preFlagTime == 0) {
                _.each(buffer, function(obj) {
                    chart.add(index, obj.data)
                    registerInfo[cfgName].seriesIdxs[i].preFlagTime = obj.ts
                })
            }
        }
    }
}

function checkBufferLen(buffer, maxLen) {
    while (buffer.length > maxLen) {
        buffer.shift()
    }
}

$.PlotMultRecords = function(cfgName, seriesName, records, extension) {
    if (typeof(cfgName) == "undefined") {
        throw "need cfgName!"
    }

    var index = -1
    var eleIndex = -1

    do {
        var cfgInfo = registerInfo[cfgName]
        if (typeof(cfgInfo) == "undefined") {
            var cfg = {
                name: cfgName,
                __isStock: true,
                title: {
                    text: cfgName
                },
                tooltip: {
                    xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
                },
                legend: {
                    enabled: true,
                },
                plotOptions: {
                    candlestick: {
                        color: '#d75442',
                        upColor: '#6ba583'
                    }
                },
                rangeSelector: {
                    buttons: [{
                        type: 'hour',
                        count: 1,
                        text: '1h'
                    }, {
                        type: 'hour',
                        count: 3,
                        text: '3h'
                    }, {
                        type: 'hour',
                        count: 8,
                        text: '8h'
                    }, {
                        type: 'all',
                        text: 'All'
                    }],
                    selected: 2,
                    inputEnabled: true
                },
                series: [{
                    type: 'candlestick',
                    name: seriesName,
                    id: seriesName,
                    data: []
                }],
            }

            if (typeof(extension) != "undefined") {
                cfg.extension = extension
            }

            registerInfo[cfgName] = {
                "cfgIdx": arrCfg.length,
                "seriesIdxs": [{
                    seriesName: seriesName,
                    index: arrCfg.length,
                    type: "candlestick",
                    preBarTime: 0
                }],
            }
            arrCfg.push(cfg)
            updateSeriesIdx()
        }

        if (!chart) {
            chart = Chart(arrCfg)
        } else {
            chart.update(arrCfg)
        }

        _.each(registerInfo[cfgName].seriesIdxs, function(ele, i) {
            if (ele.seriesName == seriesName && ele.type == "candlestick") {
                index = ele.index
                eleIndex = i
            }
        })
        if (index == -1) {
            arrCfg[registerInfo[cfgName].cfgIdx].series.push({
                type: 'candlestick',
                name: seriesName,
                id: seriesName,
                data: []
            })
            registerInfo[cfgName].seriesIdxs.push({
                seriesName: seriesName,
                index: arrCfg.length,
                type: "candlestick",
                preBarTime: 0
            })
            updateSeriesIdx()
        }
    } while (index == -1)

    for (var i = 0; i < records.length; i++) {
        if (records[i].Time == registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime) {
            chart.add(index, [records[i].Time, records[i].Open, records[i].High, records[i].Low, records[i].Close], -1)
        } else if (records[i].Time > registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime) {
            registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime = records[i].Time
            chart.add(index, [records[i].Time, records[i].Open, records[i].High, records[i].Low, records[i].Close])
        }
    }

    return chart
}

$.PlotMultLine = function(cfgName, seriesName, dot, ts, extension) {
    if (typeof(cfgName) == "undefined") {
        throw "need cfgName!"
    }

    var index = -1
    var eleIndex = -1

    do {
        var cfgInfo = registerInfo[cfgName]
        if (typeof(cfgInfo) == "undefined") {
            var cfg = {
                name: cfgName,
                __isStock: true,
                title: {
                    text: cfgName
                },
                xAxis: {
                    type: 'datetime'
                },
                series: [{
                    type: 'line',
                    name: seriesName,
                    id: seriesName,
                    data: [],
                }]
            }

            if (typeof(extension) != "undefined") {
                cfg.extension = extension
            }

            registerInfo[cfgName] = {
                "cfgIdx": arrCfg.length,
                "seriesIdxs": [{
                    seriesName: seriesName,
                    index: arrCfg.length,
                    type: "line",
                    buffer: [],
                    preDotTime: 0
                }],
            }
            arrCfg.push(cfg)
            updateSeriesIdx()
        }

        if (!chart) {
            chart = Chart(arrCfg)
        } else {
            chart.update(arrCfg)
        }

        _.each(registerInfo[cfgName].seriesIdxs, function(ele, i) {
            if (ele.seriesName == seriesName && ele.type == "line") {
                index = ele.index
                eleIndex = i
            }
        })
        if (index == -1) {
            arrCfg[registerInfo[cfgName].cfgIdx].series.push({
                type: 'line',
                name: seriesName,
                id: seriesName,
                data: [],
            })
            registerInfo[cfgName].seriesIdxs.push({
                seriesName: seriesName,
                index: arrCfg.length,
                type: "line",
                buffer: [],
                preDotTime: 0
            })
            updateSeriesIdx()
        }
    } while (index == -1)

    if (typeof(ts) == "undefined") {
        ts = new Date().getTime()
    }

    var buffer = registerInfo[cfgName].seriesIdxs[eleIndex].buffer
    if (registerInfo[cfgName].seriesIdxs[eleIndex].preDotTime != ts) {
        registerInfo[cfgName].seriesIdxs[eleIndex].preDotTime = ts
        chart.add(index, [ts, dot])
        buffer.push({
            ts: ts,
            dot: dot
        })
        checkBufferLen(buffer, maxBufferLen)
    } else {
        chart.add(index, [ts, dot], -1)
        buffer[buffer.length - 1].dot = dot
    }

    return chart
}

$.PlotMultHLine = function(cfgName, value, label, color, style) {
    if (typeof(cfgName) == "undefined" || typeof(registerInfo[cfgName]) == "undefined") {
        throw "need cfgName!"
    }

    var cfg = arrCfg[registerInfo[cfgName].cfgIdx]
    if (typeof(cfg.yAxis) == "undefined") {
        cfg.yAxis = {
            plotLines: []
        }
    } else if (typeof(cfg.yAxis.plotLines) == "undefined") {
        cfg.yAxis.plotLines = []
    }

    var obj = {
        value: value,
        color: color || 'red',
        width: 2,
        dashStyle: style || 'Solid',
        label: {
            name: label || '',
            text: (label + ":" + value) || '',
            align: 'center'
        },
    }
    var found = false
    for (var i = 0; i < cfg.yAxis.plotLines.length; i++) {
        if (cfg.yAxis.plotLines[i].label.name == label) {
            cfg.yAxis.plotLines[i] = obj
            found = true
        }
    }

    if (!found) {
        cfg.yAxis.plotLines.push(obj)
    }
    if (!chart) {
        chart = Chart(arrCfg)
    } else {
        chart.update(arrCfg)
    }

    return chart
}

$.PlotMultTitle = function(cfgName, title, chartTitle) {
    if (typeof(cfgName) == "undefined" || typeof(registerInfo[cfgName]) == "undefined") {
        throw "need cfgName!"
    }

    var cfg = arrCfg[registerInfo[cfgName].cfgIdx]

    cfg.subtitle = {
        text: title
    }

    if (typeof(chartTitle) !== 'undefined') {
        cfg.title = {
            text: chartTitle
        }
    }

    if (chart) {
        chart.update(arrCfg)
    }

    return chart
}

$.PlotMultFlag = function(cfgName, seriesName, ts, text, title, shape, color, onSeriesName) {
    if (typeof(cfgName) == "undefined" || typeof(registerInfo[cfgName]) == "undefined") {
        throw "need cfgName!"
    }

    var index = -1
    var eleIndex = -1

    do {
        if (!chart) {
            chart = Chart(arrCfg)
        } else {
            chart.update(arrCfg)
        }

        _.each(registerInfo[cfgName].seriesIdxs, function(ele, i) {
            if (ele.seriesName == seriesName && ele.type == "flag") {
                index = ele.index
                eleIndex = i
            }
        })
        if (index == -1) {
            arrCfg[registerInfo[cfgName].cfgIdx].series.push({
                type: 'flags',
                name: seriesName,
                onSeries: onSeriesName || arrCfg[registerInfo[cfgName].cfgIdx].series[0].id,
                data: []
            })
            registerInfo[cfgName].seriesIdxs.push({
                seriesName: seriesName,
                index: arrCfg.length,
                type: "flag",
                buffer: [],
                preFlagTime: 0
            })
            updateSeriesIdx()
        }
    } while (index == -1)

    if (typeof(ts) == "undefined") {
        ts = new Date().getTime()
    }

    var buffer = registerInfo[cfgName].seriesIdxs[eleIndex].buffer
    var obj = {
        x: ts,
        color: color,
        shape: shape,
        title: title,
        text: text
    }
    if (registerInfo[cfgName].seriesIdxs[eleIndex].preFlagTime != ts) {
        registerInfo[cfgName].seriesIdxs[eleIndex].preFlagTime = ts
        chart.add(index, obj)
        buffer.push({
            ts: ts,
            data: obj
        })
        checkBufferLen(buffer, maxBufferLen)
    } else {
        chart.add(index, obj, -1)
        buffer[buffer.length - 1].data = obj
    }

    return chart
}

$.GetArrCfg = function() {
    return arrCfg
}

$.removeChart = function(cfgName) {
    var index = -1
    for (var i = 0; i < arrCfg.length; i++) {
        if (arrCfg[i].name == cfgName) {
            index = i
            break
        }
    }
    if (index != -1) {
        arrCfg.splice(index, 1)
    }

    if (typeof(registerInfo[cfgName]) != "undefined") {
        delete registerInfo[cfgName]
    }
    updateSeriesIdx()
}

function init() {
    if (isChartReset) {
        Log("重置图表", "#FF0000")
        chart = Chart(arrCfg)
        chart.reset()

        Log("清空持久化数据,key:", "registerInfo、arrCfg #FF0000")
        _G("registerInfo", null)
        _G("arrCfg", null)
    } else {
        var multChartRegisterInfo = _G("registerInfo")
        var multChartArrCfg = _G("arrCfg")
        if (multChartRegisterInfo && multChartArrCfg) {
            registerInfo = multChartRegisterInfo
            arrCfg = multChartArrCfg
            Log("恢复 registerInfo、arrCfg #FF0000")
        } else {
            Log("没有数据可以恢复 #FF0000")
        }
    }
}

function onexit() {
    _G("registerInfo", registerInfo)
    _G("arrCfg", arrCfg)
    Log("保存数据,key : registerInfo, arrCfg #FF0000")
}


// test
function main() {
    LogReset(10)
    var i = 0
    var prePrintTs = 0
    var t = _C(exchange.GetTicker)
    
    while (true) {
        var r = _C(exchange.GetRecords)        
        
        $.PlotMultRecords("chart1", "kline1", r, {
            layout: 'single',
            col: 6,
            height: '600px'
        })
        $.PlotMultRecords("chart2", "kline2", r, {
            layout: 'single',
            col: 6,
            height: '600px'
        })
        $.PlotMultLine("chart2", "line1", t.Last, r[r.length - 1].Time)
        $.PlotMultLine("chart3", "line2", 10 + i)
        $.PlotMultLine("chart6", "line6", 100 + i)
        $.PlotMultLine("chart4", "line3", 1000 + i, new Date().getTime(), {
            layout: 'single',
            col: 4,
            height: '300px'
        })
        $.PlotMultLine("chart5", "line4", 10000 + i, new Date().getTime(), {
            layout: 'single',
            col: 8,
            height: '300px'
        })

        $.PlotMultHLine("chart1", r[r.length - 1].Close, "HLine1", "blue", "ShortDot")
        $.PlotMultHLine("chart4", t.Sell, "HLine2", "green")
        $.PlotMultTitle("chart3", "change : chart3->test1", "test1")

        var ts = new Date().getTime()
        if (ts - prePrintTs > 1000 * 20) {
            prePrintTs = ts
            $.PlotMultFlag("chart3", "flag1", new Date().getTime(), "flag test" + i, "flag1")
        }

        if (i == 10) {
            Log("i == 3")
            $.PlotMultFlag("chart4", "flag2", new Date().getTime(), "flag test" + i, "flag2")
            $.PlotMultFlag("chart1", "flag3", new Date().getTime(), "flag test" + i, "flag3", "squarepin", "green", "kline1")
        } else if (i == 20) {
            Log("i == 8")
            $.PlotMultLine("chart1", "line5", t.Last, r[r.length - 1].Time)
        } else if (i == 30) {
            Log("i == 10")
            $.PlotMultFlag("chart2", "flag4", new Date().getTime(), "flag test" + i, "flag4", "circlepin", "black", "kline2")
            $.removeChart("chart1")
        }
        i++
        Sleep(1000 * 1)
    }
}

그라운드:https://www.fmz.com/strategy/353264

이 자리에서 작은 꿈은 주사위를 던지고, 관심있는 사람들은 지원되는 차트 유형을 추가하고 계속 업그레이드 할 수 있습니다. 예를 들어 그림 접시 깊이 차트, 기둥 차트, 케이크 차트 등.


관련

더 많은

가벼운 구름꿈대, 초찬 [赞], 그래프 이름에서 for로 이동된 변수를 대체하면 직접 페이지가 분리되어 다른 테이블을 표시합니다.

작은 꿈예, 제목이 다르면 별도로 표시됩니다. 그러나 템플릿이 함수를 전송하는 매개 변수를 출력하는 경우 부착 또는 평면을 지정할 수 있습니다.