Skip to content

Compound.Violin

Creates a Violin Plot compound SVG Visual showing distribution density using Kernel Density Estimation (KDE)

Kernel Density Estimation (KDE)

KDE creates a smooth estimate of your data's probability density by placing a "kernel" (normal distribution curve) at each data point and summing them together. The violin plot displays this density as a symmetrical shape - wider areas indicate higher probability/frequency of values.

Key Parameters:

  • Samples: Controls the resolution of the density calculation (higher = smoother, but slower performance)

  • Bandwidth: Controls how much each data point influences nearby areas. Smaller bandwidth values create sharper, more detailed curves that closely follow individual data points. Larger bandwidth values create smoother, more generalized shapes that show overall trends

DaxLib.SVG.Compound.Violin( x, y, width, height, paddingX, paddingY, axisRef, measureRef, samples, bandwidth, color, orientation )
Parameter Type Required Description
x INT64 The x position of the compound
y INT64 The y position of the compound
width INT64 The width of the compound
height INT64 The height of the compound
paddingX DECIMAL Optional: The horizontal padding percentage (0.0-1.0, e.g., 0.1 = 10% padding). Defaults to 0
paddingY DECIMAL Optional: The vertical padding percentage (0.0-1.0, e.g., 0.1 = 10% padding). Defaults to 0
axisRef ANYREF EXPR The column that the measure will be evaluated against
measureRef NUMERIC EXPR The measure to evaluate
samples INT64 Number of density calculation points
bandwidth NUMERIC Kernel bandwidth for smoothing
color STRING Fill color for the violin shape
orientation STRING Optional: "Horizontal" (default) or "Vertical"

STRING SVG Violin Plot

DaxLib.SVG.SVG(
    500,
    100,
    BLANK(),
    DaxLib.SVG.Compound.Violin(
        0,                  // x
        0,                  // y
        500,                // width
        100,                // height
        0.05,               // paddingX
        0.02,               // paddingY
        Dates[Date],        // axisRef
        [Total Cost],       // measureRef
        MAX( Samples[Samples] ), // samples
        MAX( Bandwidth[Bandwidth] ), // bandwidth
        "#EC008C",          // color
        "Horizontal"        // orientation
    ),
    BLANK()
)
function 'DaxLib.SVG.Compound.Violin' =
        (
            x: INT64,
            y: INT64,
            width: INT64,
            height: INT64,
            paddingX: DOUBLE,
            paddingY: DOUBLE,
            axisRef: ANYREF EXPR,
            measureRef: NUMERIC EXPR,
            samples: INT64,
            bandwidth: NUMERIC,
            color: STRING,
            orientation: STRING
        ) =>

            // Apply padding to dimensions
            VAR _X =            x + (width * (IF(ISBLANK(paddingX), 0, paddingX) / 2))
            VAR _Y =            y + (height * (IF(ISBLANK(paddingY), 0, paddingY) / 2))
            VAR _Width =        width * (1 - IF(ISBLANK(paddingX), 0, paddingX))
            VAR _Height =       height * (1 - IF(ISBLANK(paddingY), 0, paddingY))

            // Check if Axis is numeric
            VAR axisSample =    MAX( axisRef )
            VAR axisIsNumeric = ISNUMERIC( axisSample ) || ISDATETIME( axisSample )

            // For totals
            VAR _Data = 
                ADDCOLUMNS(
                    FILTER(
                        VALUES( axisRef ),
                        NOT ISBLANK( measureRef )
                    ),
                    "@AxisIndex",   
                        IF(
                            axisIsNumeric,
                            axisRef,
                            RANK( DENSE, CALCULATETABLE( VALUES( axisRef ), ALLSELECTED() ) )
                        ),
                    "@Value", measureRef
                )

            VAR _NumValues =        COUNTROWS( _Data )
            VAR _Min =              MINX( _Data, [@Value] )
            VAR _Max =              MAXX( _Data, [@Value] )
            VAR _Range =            _Max - _Min
            VAR _RangePerSample =   _Range / samples

            // Calculate Kernel Density Estimation using Normal distribution
            VAR _KDE = 
                ADDCOLUMNS(
                    GENERATESERIES( 0, samples + 1, 1 ),
                    "@InputX", _Min + _RangePerSample * [Value],
                    "@KDE", 
                        ( 1 / _NumValues ) * 
                        SUMX(
                            _Data, 
                            NORM.DIST( 
                                _Min + _RangePerSample * [Value], 
                                [@Value], 
                                bandwidth, 
                                FALSE 
                            ) 
                        )
                )

            VAR _MaxKDE =       MAXX( _KDE, [@KDE] )

            VAR _Orientation = IF( orientation = "Vertical", "Vertical", "Horizontal" )

            // Map KDE values to SVG coordinates using normalize function
            VAR _Points = 
                ADDCOLUMNS(
                    _KDE,
                    "@X", IF( _Orientation = "Horizontal",
                        DaxLib.SVG.Scale.Normalize( [@InputX], _Min, _Max, _X, _X + _Width ),
                        DaxLib.SVG.Scale.Normalize( [@KDE], 0, _MaxKDE, _X + _Width * 0.5, _X + _Width )
                    ),
                    "@Y", IF( _Orientation = "Horizontal",
                        DaxLib.SVG.Scale.Normalize( [@KDE], 0, _MaxKDE, _Y + _Height * 0.5, _Y ),
                        DaxLib.SVG.Scale.Normalize( [@InputX], _Min, _Max, _Y + _Height, _Y )
                    )
                )

            // Create control points for smooth Bézier curves
            VAR _PointsWithPrev = 
                NATURALLEFTOUTERJOIN(
                    _Points,
                    SELECTCOLUMNS(
                        _Points,
                        "Value", [Value] + 1,
                        "@PrevX", [@X],
                        "@PrevY", [@Y]
                    )
                )

            VAR _WithControlPoints = 
                ADDCOLUMNS(
                    _PointsWithPrev,
                    "@CX", [@prevX] + ( ( [@x] - [@prevX] ) / 2 ),
                    "@CY", [@y]
                )

        // Create the violin shape as a single closed path
        // Start at the center-left, go up the top curve, then down the bottom curve, and close
        VAR _FirstPoint = MINX( _Points, [@X] )
        VAR _LastPoint = MAXX( _Points, [@X] )
        VAR _CenterY = _Y + (_Height * 0.5)
        VAR _CenterX = _X + (_Width * 0.5)

        // Top/Right half curve
        VAR _TopCurve = 
            CONCATENATEX(
                _WithControlPoints,
                IF(
                    [Value] = 0,
                    IF( _Orientation = "Horizontal", "M " & [@X] & " " & _CenterY, "M " & _CenterX & " " & [@Y] ),
                    "S " & [@CX] & " " & [@CY] & ", " & [@X] & " " & [@Y]
                ),
                " ",
                [Value],
                ASC
            )

        // Bottom/Left half curve (mirrored) – uses S Bézier for smooth mirror with control points
        VAR _PointsWithNext = 
            NATURALLEFTOUTERJOIN(
                _Points,
                SELECTCOLUMNS(
                    _Points,
                    "Value", [Value] - 1,
                    "@NextX", [@X],
                    "@NextY", [@Y]
                )
            )

        VAR _BottomCurve = 
            CONCATENATEX(
                _PointsWithNext,
                VAR _MirroredX = IF( _Orientation = "Horizontal", [@X], _CenterX + (_CenterX - [@X]) )
                VAR _MirroredY = IF( _Orientation = "Horizontal", _CenterY + (_CenterY - [@Y]), [@Y] )
                VAR _MCX = IF( _Orientation = "Horizontal",
                    [@NextX] + ( [@X] - [@NextX] ) / 2,
                    _CenterX + ( _CenterX - ( [@NextX] + ( [@X] - [@NextX] ) / 2 ) )
                )
                VAR _MCY = IF( _Orientation = "Horizontal",
                    _CenterY + ( _CenterY - [@Y] ),
                    [@NextY] + ( [@Y] - [@NextY] ) / 2
                )
                RETURN
                    IF(
                        ISBLANK( [@NextX] ),
                        "L " & _MirroredX & " " & _MirroredY,
                        IF(
                            [Value] = 0,
                            IF( _Orientation = "Horizontal",
                                "S " & _MCX & " " & _CenterY & ", " & [@X] & " " & _CenterY,
                                "S " & _CenterX & " " & _MCY & ", " & _CenterX & " " & [@Y]
                            ),
                            "S " & _MCX & " " & _MCY & ", " & _MirroredX & " " & _MirroredY
                        )
                    ),
                " ",
                [Value],
                DESC
            )           // Create a single closed path for the violin shape
            VAR _ViolinPath = 
                _TopCurve & 
                " " & _BottomCurve & 
                " Z" // Close the path

            // Combined Elements
            VAR _CombinedElements = 
                DaxLib.SVG.Element.Paths(
                    _ViolinPath, // d
                    DaxLib.SVG.Attr.Shapes(
                        color,          // fill
                        0.5,            // fillOpacity
                        BLANK(),        // fillRule
                        color,          // stroke
                        1,              // strokeWidth
                        BLANK(),        // strokeOpacity
                        BLANK()         // opacity
                    ),
                    BLANK()             // transforms
                )

            RETURN

                IF( NOT ISEMPTY( _Data ), _CombinedElements )