Watchlist Heatmap Plus
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © faiyaz7283
//@version=5

indicator(title="Watchlist Heatmap Plus", shorttitle="WL-Plus", overlay=true)

// ::Imports:: {

import faiyaz7283/tools/10 as tools
import faiyaz7283/printer/6 as prnt
import faiyaz7283/multidata/7 as mltd

// }

// ::Inputs:: {

var section00 = 'HEADER PREFIX'
//---------------------:
var headerPrefix = input.string(defval='', title='Header Prefix', tooltip='Customize this watchlist with a name prefix.', 
  group=section00)


var section01 = 'POSITION, SIZE & COLORS'
//---------------------:

var displayLoc = input.string(defval = position.top_right, title = 'Display Location', options = [position.top_left, 
  position.top_center, position.top_right, position.middle_left, position.middle_center, position.middle_right, 
  position.bottom_left, position.bottom_center, position.bottom_right], group = section01)

var cellPad = input.int(defval = 1, title = 'Cell Spacing', minval = 0, maxval = 5, group = section01)

var orientation = input.string(defval = 'horizontal', title = 'Orientation',  options = ['horizontal', 'vertical'], 
  group = section01) == 'vertical' ? false : true 

var grdUp = input.color(defval = #00FF00, title = '⬆ Max', inline = 'grdnt', group = section01)

var grdNeutral = input.color(defval = #fff9c4, title = '⮕ Neutral', inline = 'grdnt', group = section01)

var grdDown = input.color(defval = #FF0000, title = '⬇ Min', inline = 'grdnt', group = section01)

var headerColor = input.color(defval = #ffcc80, title = 'Header:\tColor', inline = 'hdrCl', group = section01)

var headerSize = input.string(defval=size.normal, title = 'Size', options = ['hide', size.auto, size.tiny, size.small, 
  size.normal, size.large, size.huge], inline = 'hdrCl', group = section01)

var headerAlign = input.string(defval=text.align_center, title = 'Align', 
  options = [text.align_left, text.align_center, text.align_right], inline = 'hdrCl', group = section01)

var pkSize = input.string(defval=size.auto, title = 'Primary Key:\tSize', options = [size.auto, size.tiny, size.small, 
  size.normal, size.large, size.huge], inline = 'pkCl', group = section01)

var pkAlign = input.string(defval=text.align_center, title = 'Align', 
  options = [text.align_left, text.align_center, text.align_right], inline = 'pkCl', group = section01)

var pkOffset = input.string(defval='text', title = 'Primary Key:\tOffset Item', options = ['text', 'background'], 
  inline = 'pkCl2', group = section01) == 'background' ? 'bg' : 'text'

var pkOffsetColor = input.color(defval = #000000, title = 'Offset Color', inline = 'pkCl2', group = section01)

var dkBgColor = input.color(defval = #666E8C1A, title = 'Data Key:\tBg', inline = 'dkCl', group = section01)

var dkTextColor = input.color(defval = #ffffff, title = 'Text', inline = 'dkCl', group = section01)

var dkSize = input.string(defval=size.auto, title = 'Size', options = [size.auto, size.tiny, size.small, 
  size.normal, size.large, size.huge], inline = 'dkCl', group = section01)

var dkAlign = input.string(defval=text.align_center, title = 'Align', 
  options = [text.align_left, text.align_center, text.align_right], inline = 'dkCl', group = section01)

var dkOffset = input.string(defval='text', title = 'Data Key:\tOffset Item', options = ['text', 'background'], 
  inline = 'dkCl2', group = section01) == 'background' ? 'bg' : 'text'

var dkOffsetColor = input.color(defval = #000000, title = 'Offset Color', inline = 'dkCl2', group = section01)

var textSize = input.string(defval=size.auto, title = 'Value:\tSize', options = [size.auto, size.tiny, size.small, 
  size.normal, size.large, size.huge], inline = 'valueCl', group = section01)

var textAlign = input.string(defval=text.align_right, title = 'Align', 
  options = [text.align_left, text.align_center, text.align_right], inline = 'valueCl', group = section01)

var textOffset = input.string(defval='text', title = 'Value:\tOffset Item', options = ['text', 'background'], 
  inline = 'valueCl2', group = section01) == 'background' ? 'bg' : 'text'

var textOffsetColor = input.color(defval = #000000, title = 'Offset Color', inline = 'valueCl2', group = section01)


var section02 = 'DATA DISPLAY'
//----------------------------:

var nthTip1 = 'N: nth candle\n\n- Nth 0 is current candle.\n- Nth 1 is previous candle.\n- Nth 2 is previous '
var nthTip = nthTip1 + '2nd candle in past.\n- Nth 3 is previous 3rd candle in past.\n ...'
var nth = input.int(defval=0, title = 'History: Nth', minval=0, tooltip=nthTip, inline = 'tf', group = section02)
var nthKey = nth > 0 ? (nth == 1 ? 'Previous ' : str.format('(Past {0}) ', tools.ordinal(nth))) : na

var showOpen = input.bool(defval = true, title = 'Open', inline='dk', group = section02)
var showHigh = input.bool(defval = true, title = 'High', inline='dk', group = section02)
var showLow = input.bool(defval = true, title = 'Low', inline='dk', group = section02)
var showClose = input.bool(defval = true, title = 'Close', inline='dk', group = section02)
var showCloseP = input.bool(defval = true, title = 'Close%', inline='dk', group = section02)
var showVol = input.bool(defval = true, title = 'Vol', inline='dk', group = section02)
var showVolP = input.bool(defval = true, title = 'Vol%', inline='dk', group = section02)

var dsplTip0 = 'Display Data Options:\n\n'
var dsplTip1 = dsplTip0 + 'regular:\nReturns the standard value corresponding to the data key (e.g., open, high, low, close, volume).\n\n'
var dsplTip2 = dsplTip1 + 'change:\nReturns the difference between two values.\n\n'
var dsplTip3 = dsplTip2 + 'change percent:\nReturns the percentage difference between two values.\n\n'
var dsplTip4 = dsplTip3 + 'equal to:\nReturns \'true\' if the two values are equal; otherwise, \'false\'.\n\n'
var dsplTip5 = dsplTip4 + 'less than:\nReturns \'true\' if the first value is less than the second value; otherwise, \'false\'.\n\n'
var dsplTip6 = dsplTip5 + 'less than or equal to:\nReturns \'true\' if the first value is less than or equal to the second value; otherwise, \'false\'.\n\n'
var dsplTip7 = dsplTip6 + 'greater than:\nReturns \'true\' if the first value is greater than the second value; otherwise, \'false\'.\n\n'
var dsplTip8 = dsplTip7 + 'greater than or equal to:\nReturns \'true\' if the first value is greater than or equal to the second value; otherwise,  \'false\'.'

var displayValues = input.string(defval ='regular', title = 'Display Data Type', options = ['regular', 'change', 
  'change percent', 'equal to', 'less than', 'less than or equal to', 'greater than', 'greater than or equal to'], 
  inline='/', tooltip=dsplTip8, group = section02)

var sortByKey = input.string(defval ='close', title = 'Sort By Key', options = ['open', 'high', 'low', 'close', 'volume'], 
  inline = '-', group = section02)

var sortByOrder = input.string(defval ='descending', title = 'Sort Order', options = ['descending', 'ascending'], 
  inline = '-', group = section02)

var asc = sortByOrder == 'ascending' ? true : false 

var section03 = 'CUSTOM TIMEFRAME'
//--------------------------------:

var tfNote0 = "M: Multiplier | U: Unit\n\nValid Multipliers per Unit:\n- Seconds, ONLY use 1, 5, 10, 15 or 30."
var tfNote1 = tfNote0 + '\n- Minutes, 1 to 1439.\n- Days, 1 to 365.\n- Weeks, 1 to 52.\n- Months, 1 to 12.\n\n'
var tfNote = tfNote1 + nthTip + '\n\nCheck box to display.'
var tfMultiplier = input.int(defval = 1, title = 'M', minval = 1, maxval = 1439, inline = 'tf', 
  group = section03)
var tfUnits = input.timeframe(defval = 'Days', title = 'U', options = ['Seconds', 'Minutes', 'Days', 'Weeks', 
  'Months'], inline = 'tf', group = section03)
var tfNth = input.int(defval=0, title = 'N', minval=0, inline = 'tf', group = section03)
var customTf = input.bool(defval = false, title = 'Apply', tooltip = tfNote, inline = 'tf', group = section03)
nthKey := customTf ? (tfNth > 0 ? (tfNth == 1 ? 'Previous ' : str.format('(Past {0}) ', tools.ordinal(tfNth))) : na) : nthKey

var unit = switch tfUnits
    'Seconds' => 'S'
    'Minutes' => ''
    'Days'    => 'D'
    'Weeks'   => 'W'
    'Months'  => 'M'

var tf = str.tostring(tfMultiplier) + unit

var section04 = 'WATCHLIST SYMBOLS'
//--------------------------------:

var symbol01  = input.symbol(defval = '', title = '01. ', inline = 'r01', group = section04)
var symbol02  = input.symbol(defval = '', title = '02. ', inline = 'r01', group = section04)
var symbol03  = input.symbol(defval = '', title = '03. ', inline = 'r02', group = section04)
var symbol04  = input.symbol(defval = '', title = '04. ', inline = 'r02', group = section04)
var symbol05  = input.symbol(defval = '', title = '05. ', inline = 'r03', group = section04)
var symbol06  = input.symbol(defval = '', title = '06. ', inline = 'r03', group = section04)
var symbol07  = input.symbol(defval = '', title = '07. ', inline = 'r04', group = section04)
var symbol08  = input.symbol(defval = '', title = '08. ', inline = 'r04', group = section04)
var symbol09  = input.symbol(defval = '', title = '09. ', inline = 'r05', group = section04)
var symbol10  = input.symbol(defval = '', title = '10. ', inline = 'r05', group = section04)
var symbol11  = input.symbol(defval = '', title = '11. ', inline = 'r06', group = section04)
var symbol12  = input.symbol(defval = '', title = '12. ', inline = 'r06', group = section04)
var symbol13  = input.symbol(defval = '', title = '13. ', inline = 'r07', group = section04)
var symbol14  = input.symbol(defval = '', title = '14. ', inline = 'r07', group = section04)
var symbol15  = input.symbol(defval = '', title = '15. ', inline = 'r08', group = section04)
var symbol16  = input.symbol(defval = '', title = '16. ', inline = 'r08', group = section04)
var symbol17  = input.symbol(defval = '', title = '17. ', inline = 'r09', group = section04)
var symbol18  = input.symbol(defval = '', title = '18. ', inline = 'r09', group = section04)
var symbol19  = input.symbol(defval = '', title = '19. ', inline = 'r10', group = section04)
var symbol20  = input.symbol(defval = '', title = '20. ', inline = 'r10', group = section04)
var symbol21  = input.symbol(defval = '', title = '21. ', inline = 'r11', group = section04)
var symbol22  = input.symbol(defval = '', title = '22. ', inline = 'r11', group = section04)
var symbol23  = input.symbol(defval = '', title = '23. ', inline = 'r12', group = section04)
var symbol24  = input.symbol(defval = '', title = '24. ', inline = 'r12', group = section04)
var symbol25  = input.symbol(defval = '', title = '25. ', inline = 'r13', group = section04)
var symbol26  = input.symbol(defval = '', title = '26. ', inline = 'r13', group = section04)
var symbol27  = input.symbol(defval = '', title = '27. ', inline = 'r14', group = section04)
var symbol28  = input.symbol(defval = '', title = '28. ', inline = 'r14', group = section04)
var symbol29  = input.symbol(defval = '', title = '29. ', inline = 'r15', group = section04)
var symbol30  = input.symbol(defval = '', title = '30. ', inline = 'r15', group = section04)
var symbol31  = input.symbol(defval = '', title = '31. ', inline = 'r16', group = section04)
var symbol32  = input.symbol(defval = '', title = '32. ', inline = 'r16', group = section04)
var symbol33  = input.symbol(defval = '', title = '33. ', inline = 'r17', group = section04)
var symbol34  = input.symbol(defval = '', title = '34. ', inline = 'r17', group = section04)
var symbol35  = input.symbol(defval = '', title = '35. ', inline = 'r18', group = section04)
var symbol36  = input.symbol(defval = '', title = '36. ', inline = 'r18', group = section04)
var symbol37  = input.symbol(defval = '', title = '37. ', inline = 'r19', group = section04)
var symbol38  = input.symbol(defval = '', title = '38. ', inline = 'r19', group = section04)
var symbol39  = input.symbol(defval = '', title = '39. ', inline = 'r20', group = section04)
var symbol40  = input.symbol(defval = '', title = '40. ', inline = 'r20', group = section04)
var smblNote1 = 'Please note that once selected, symbols cannot be entirely removed from this form; ' 
var smblNote2 = smblNote1 + 'they can only be replaced with other symbols. To remove one or more symbols, you have two '
var smblNote3 = smblNote2 + 'options: either click \"reset settings\" or utilize this feature to exhibit the total '
var smblNote4 = smblNote3 + 'desired number of symbols, regardless of the existing count on the watchlist form.'
var totSym = input.int(defval = 40, title = 'How many watchlist symbols to display?', minval = 0, maxval = 40, 
  tooltip = smblNote4, group = section04)

// }

// ::Functions:: {

sortByKey() =>
    switch sortByKey
        'open' => showOpen ? 'open' : na
        'high' => showHigh ? 'high' : na
        'low' => showLow ? 'low' : na
        'close' => showClose ? 'close' : na
        'volume' => showVol ? 'volume' : na

displayTf() =>
    mp = customTf ? tfMultiplier : timeframe.multiplier
    ut = customTf ? unit : timeframe.period
    _m = mp
    _u = switch 
        str.endswith(ut, "S") => 
            mp == 1 ? 'Second' : 'Seconds'
        str.endswith(ut, "D") =>
            mp == 1 ? 'Day' : 'Days'
        str.endswith(ut, "W") =>
            mp == 1 ? 'Week' : 'Weeks'
        str.endswith(ut, "M") =>
            if mp == 1
                'Month'
            else if mp == 12
                _m := 1
                'Year'
            else
                'Months'
        =>
            if mp == 1
                'Minute'
            else if mp % 60 == 0
                _m := mp / 60
                _m == 1 ? 'Hour' : 'Hours'
            else
                'Minutes'
    _m == 1 ? str.format('{0}{1}', nthKey, _u) : str.format('{0}{1} {2}', nthKey, _m, _u)

displayValues() =>
    switch displayValues
        'regular' => na
        'change' => 'change_format'
        'change percent' => 'change_percent_format'
        'equal to' => 'et'
        'less than' => 'lt'
        'less than or equal to' => 'lte'
        'greater than' => 'gt'
        'greater than or equal to' => 'gte'

getPkv(simple string ticker) =>
    t = customTf ? tf : ''
    p = customTf ? tfNth : nth
    pk = syminfo.ticker(ticker)
    tckr = ticker.new(syminfo.prefix(ticker), pk, syminfo.session)
    clp = (close[p] - close[p + 1]) / close[p + 1] * 100
    vlp = (volume[p] - volume[p + 1]) / volume[p + 1] * 100
    [o, o1, h, h1, l, l1, c, c1, v, v1, cp, vp] = request.security(tckr, t, [open[p], open[p + 1], high[p], high[p + 1], 
      low[p], low[p + 1], close[p], close[p + 1], volume[p], volume[p + 1], clp, vlp], ignore_invalid_symbol = true)

    __kvs__ = array.new<mltd.kv>()

    if showOpen
        __kvs__.push(mltd.kv('open', o, o1, format='{0,number,currency}'))

    if showHigh
        __kvs__.push(mltd.kv('high', h, h1, format='{0,number,currency}'))

    if showLow
        __kvs__.push(mltd.kv('low', l, l1, format='{0,number,currency}'))

    if showClose
        __kvs__.push(mltd.kv('close', c, c1, format='{0,number,currency}'))

    if displayValues == 'regular' and showCloseP
        __kvs__.push(mltd.kv('close%', val=cp, format='{0,number,0.00}%'))

    if showVol
        __kvs__.push(mltd.kv('volume', v, v1, format=tools.numCompact(v), changeFormat=tools.numCompact(v-v1)))

    if displayValues == 'regular' and showVolP
        __kvs__.push(mltd.kv('volume%', val=vp, format='{0,number,0.00}%'))
        
    if __kvs__.size() == 0
        runtime.error("Required: Atleast one data key must be selected for display.")

    mltd.pkv(pk, __kvs__)

// }

if barstate.islast
    // ::Data:: {
    symbols = array.new<mltd.pkv>()

    if tools._bool(symbol01) and totSym >= 1
        symbols.push(getPkv(symbol01))

    if tools._bool(symbol02) and totSym >= 2
        symbols.push(getPkv(symbol02))

    if tools._bool(symbol03) and totSym >= 3
        symbols.push(getPkv(symbol03))

    if tools._bool(symbol04) and totSym >= 4
        symbols.push(getPkv(symbol04))

    if tools._bool(symbol05) and totSym >= 5
        symbols.push(getPkv(symbol05))

    if tools._bool(symbol06) and totSym >= 6
        symbols.push(getPkv(symbol06))

    if tools._bool(symbol07) and totSym >= 7
        symbols.push(getPkv(symbol07))

    if tools._bool(symbol08) and totSym >= 8
        symbols.push(getPkv(symbol08))

    if tools._bool(symbol09) and totSym >= 9
        symbols.push(getPkv(symbol09))

    if tools._bool(symbol10) and totSym >= 10
        symbols.push(getPkv(symbol10))

    if tools._bool(symbol11) and totSym >= 11
        symbols.push(getPkv(symbol11))

    if tools._bool(symbol12) and totSym >= 12
        symbols.push(getPkv(symbol12))

    if tools._bool(symbol13) and totSym >= 13
        symbols.push(getPkv(symbol13))

    if tools._bool(symbol14) and totSym >= 14
        symbols.push(getPkv(symbol14))

    if tools._bool(symbol15) and totSym >= 15
        symbols.push(getPkv(symbol15))

    if tools._bool(symbol16) and totSym >= 16
        symbols.push(getPkv(symbol16))

    if tools._bool(symbol17) and totSym >= 17
        symbols.push(getPkv(symbol17))

    if tools._bool(symbol18) and totSym >= 18
        symbols.push(getPkv(symbol18))

    if tools._bool(symbol19) and totSym >= 19
        symbols.push(getPkv(symbol19))

    if tools._bool(symbol20) and totSym >= 20
        symbols.push(getPkv(symbol20))

    if tools._bool(symbol21) and totSym >= 21
        symbols.push(getPkv(symbol21))

    if tools._bool(symbol22) and totSym >= 22
        symbols.push(getPkv(symbol22))

    if tools._bool(symbol23) and totSym >= 23
        symbols.push(getPkv(symbol23))

    if tools._bool(symbol24) and totSym >= 24
        symbols.push(getPkv(symbol24))

    if tools._bool(symbol25) and totSym >= 25
        symbols.push(getPkv(symbol25))

    if tools._bool(symbol26) and totSym >= 26
        symbols.push(getPkv(symbol26))

    if tools._bool(symbol27) and totSym >= 27
        symbols.push(getPkv(symbol27))

    if tools._bool(symbol28) and totSym >= 28
        symbols.push(getPkv(symbol28))

    if tools._bool(symbol29) and totSym >= 29
        symbols.push(getPkv(symbol29))

    if tools._bool(symbol30) and totSym >= 30
        symbols.push(getPkv(symbol30))

    if tools._bool(symbol31) and totSym >= 31
        symbols.push(getPkv(symbol31))

    if tools._bool(symbol32) and totSym >= 32
        symbols.push(getPkv(symbol32))

    if tools._bool(symbol33) and totSym >= 33
        symbols.push(getPkv(symbol33))

    if tools._bool(symbol34) and totSym >= 34
        symbols.push(getPkv(symbol34))

    if tools._bool(symbol35) and totSym >= 35
        symbols.push(getPkv(symbol35))

    if tools._bool(symbol36) and totSym >= 36
        symbols.push(getPkv(symbol36))

    if tools._bool(symbol37) and totSym >= 37
        symbols.push(getPkv(symbol37))

    if tools._bool(symbol38) and totSym >= 38
        symbols.push(getPkv(symbol38))

    if tools._bool(symbol39) and totSym >= 39
        symbols.push(getPkv(symbol39))

    if tools._bool(symbol40) and totSym >= 40
        symbols.push(getPkv(symbol40))

    // }

    if symbols.size() > 0
        tbs = prnt.tableStyle.new(bgColor=na, frameColor=na, borderWidth = cellPad)
        hs = prnt.headerStyle.new(bgColor=na, textHalign=headerAlign, textColor=headerColor, textSize=headerSize)
        cs = prnt.cellStyle.new(horizontal=orientation, textHalign=textAlign, textSize=textSize)

        // Initialize the printer.
        hd = headerPrefix + (asc ? ' ↑ ': ' ↓ ') + displayTf()
        header = headerSize != 'hide' ? hd : na
        printer = prnt.printer(header=header, tableStyle=tbs, headerStyle=hs, cellStyle=cs, stack=orientation, 
          loc=displayLoc)

        // create the data3d object
        sortByKey := sortByKey()
        d3d = mltd.data3d(symbols, sort=(not na(sortByKey) ? true : false), asc=asc, sortByKey=sortByKey, 
          sortByChange=true)

        // Dynamic values
        array<float> _closeDv = na
        prnt.dvs _closeDvs = na
        float _closeDvMax = na
        float _closeDvMin = na
        array<float> _volumeDv = na
        prnt.dvs _volumeDvs = na
        float _volumeDvMax = na
        float _volumeDvMin = na
        float _sortedDvMax = na
        float _sortedDvMin = na
        d3dDv = map.new<string, prnt.dvs>()
        d3d_styles = map.new<string, prnt.cellStyle>()
        dc_val_color = prnt.dynamicColor.new(up=grdUp, neutral=grdNeutral, down=grdDown, offsetItem='bg')
        dc_sorted_val_color = prnt.dynamicColor.new(up=grdUp, neutral=grdNeutral, down=grdDown, offsetItem=textOffset, 
          offsetColor=textOffsetColor)

        // Data keys:
        // open - 
        if showOpen
            _openDv = d3d.dkChangeValues('open', true)
            _openDvMax = _openDv.max()
            _openDvMin = _openDv.min()
            _sortedDvMax := sortByKey == 'open' ? _openDvMax : _sortedDvMax
            _sortedDvMin := sortByKey == 'open' ? _openDvMin : _sortedDvMin
            d3dDv.put('open', _openDv.dvs())
            styleOpen = prnt.cellStyle.copy(cs)
            styleOpen.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'open' ? dc_sorted_val_color : dc_val_color)
            styleOpen.dynamicColor.numberUp := _openDvMax
            styleOpen.dynamicColor.numberDown := _openDvMin
            styleOpen.gradient := true
            d3d_styles.put('open', styleOpen)
        
        // high - 
        if showHigh
            _highDv = d3d.dkChangeValues('high', true)
            _highDvMax = _highDv.max()
            _highDvMin = _highDv.min()
            _sortedDvMax := sortByKey == 'high' ? _highDvMax : _sortedDvMax
            _sortedDvMin := sortByKey == 'high' ? _highDvMin : _sortedDvMin
            d3dDv.put('high', _highDv.dvs())
            styleHigh = prnt.cellStyle.copy(cs)
            styleHigh.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'high' ? dc_sorted_val_color : dc_val_color)
            styleHigh.dynamicColor.numberUp := _highDv.max()
            styleHigh.dynamicColor.numberDown := _highDv.min()
            styleHigh.gradient := true
            d3d_styles.put('high', styleHigh)

        // low - 
        if showLow
            _lowDv = d3d.dkChangeValues('low', true)
            _lowDvMax = _lowDv.max()
            _lowDvMin = _lowDv.min()
            _sortedDvMax := sortByKey == 'low' ? _lowDvMax : _sortedDvMax
            _sortedDvMin := sortByKey == 'low' ? _lowDvMin : _sortedDvMin
            d3dDv.put('low', _lowDv.dvs())
            styleLow = prnt.cellStyle.copy(cs)
            styleLow.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'low' ? dc_sorted_val_color : dc_val_color)
            styleLow.dynamicColor.numberUp := _lowDv.max()
            styleLow.dynamicColor.numberDown := _lowDv.min()
            styleLow.gradient := true
            d3d_styles.put('low', styleLow)

        // close - 
        if showClose
            _closeDv := d3d.dkChangeValues('close', true)
            _closeDvs := _closeDv.dvs()
            _closeDvMax := _closeDv.max()
            _closeDvMin := _closeDv.min()
            _sortedDvMax := sortByKey == 'close' ? _closeDvMax : _sortedDvMax
            _sortedDvMin := sortByKey == 'close' ? _closeDvMin : _sortedDvMin
            d3dDv.put('close', _closeDvs)
            styleClose = prnt.cellStyle.copy(cs)
            styleClose.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'close' ? dc_sorted_val_color : dc_val_color)
            styleClose.dynamicColor.numberUp := _closeDvMax
            styleClose.dynamicColor.numberDown := _closeDvMin
            styleClose.gradient := true
            d3d_styles.put('close', styleClose)

        // close% -
        if displayValues == 'regular' and showCloseP
            if not showClose
                _closeDv := d3d.dkFloatValues('close%')
                _closeDvs := _closeDv.dvs()
                _closeDvMax := _closeDv.max()
                _closeDvMin := _closeDv.min()
            d3dDv.put('close%', _closeDvs)
            styleCloseP = prnt.cellStyle.copy(cs)
            styleCloseP.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'close' ? dc_sorted_val_color : dc_val_color)
            styleCloseP.dynamicColor.numberUp := _closeDvMax
            styleCloseP.dynamicColor.numberDown := _closeDvMin
            styleCloseP.gradient := true
            d3d_styles.put('close%', styleCloseP)

        // volume - 
        if showVol
            _volumeDv := d3d.dkChangeValues('volume', true)
            _volumeDvs := _volumeDv.dvs()
            _volumeDvMax := _volumeDv.max()
            _volumeDvMin := _volumeDv.min()
            _sortedDvMax := sortByKey == 'volume' ? _volumeDvMax : _sortedDvMax
            _sortedDvMin := sortByKey == 'volume' ? _volumeDvMin : _sortedDvMin
            d3dDv.put('volume', _volumeDvs)
            styleVol = prnt.cellStyle.copy(cs)
            styleVol.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'volume' ? dc_sorted_val_color : dc_val_color)
            styleVol.dynamicColor.numberUp := _volumeDvMax
            styleVol.dynamicColor.numberDown := _volumeDvMin
            styleVol.gradient := true
            d3d_styles.put('volume', styleVol)
    
        // volume% - 
        if displayValues == 'regular' and showVolP
            if not showVol
                _volumeDv := d3d.dkFloatValues('volume%')
                _volumeDvs := _volumeDv.dvs()
                _volumeDvMax := _volumeDv.max()
                _volumeDvMin := _volumeDv.min()
            d3dDv.put('volume%', _volumeDvs)
            styleVolP = prnt.cellStyle.copy(cs)
            styleVolP.dynamicColor := prnt.dynamicColor.copy(sortByKey == 'volume' ? dc_sorted_val_color : dc_val_color)
            styleVolP.dynamicColor.numberUp := _volumeDvMax
            styleVolP.dynamicColor.numberDown := _volumeDvMin
            styleVolP.gradient := true
            d3d_styles.put('volume%', styleVolP)

        // Primary keys -
        stylePk = prnt.cellStyle.new(textHalign=pkAlign, textSize=pkSize)
        if not na(sortByKey)
            stylePk.dynamicColor := prnt.dynamicColor.new(up=grdUp, neutral=grdNeutral, down=grdDown, 
              offsetItem=pkOffset, offsetColor=pkOffsetColor, numberUp=_sortedDvMax, numberDown=_sortedDvMin)
            stylePk.gradient := true
        else
            stylePk.textColor := dkTextColor
            stylePk.bgColor := dkBgColor
        d3d_styles.put('pk', stylePk)

        // Data keys -
        styleDk = prnt.cellStyle.new(textHalign=dkAlign, textColor=dkTextColor, textSize=dkSize, bgColor=dkBgColor)
        d3d_styles.put('dk', styleDk)    

        printer.print(d3d, styles=d3d_styles, displayValues=displayValues(), dynamicValues=d3dDv, dynamicKey=sortByKey)
