← Go Back

SVG Apple Fitness Rings

In this project, I wanted to see if I could recreate the fitness rings that I see on the Apple Fitness app. I started using Adobe Illustrator to create the rings and exported them as SVG. Then I modified the code to work with the data. In the code below, you will see that I added some settings variables to allow you to change the visuals without having to modify the actual code. You can change the ring width, animation speed, and colors.

For this visual to work, you will need to make sure you set your values and targets, along with importing the HTML Content visual. Once you have that visual in Power BI, you can use this code to create a measure referencing your data.

Source code
    
        Apple Fitness Rings = 

        //www.adamcampbell.tech 
        // Be sure to import the visual HTML Content so you can use this measure. 
        
        // Ring Settings 
        
        VAR Rings_Width = 35 // Width of each ring
        VAR Circle_BG = "#00000000"  //Set the circle background. Currently transparent. 
        
        VAR Ring1_BG_Color = "#091b21" // Dark Red
        VAR Ring2_BG_Color = "#182313" // Dark Green
        VAR Ring3_BG_Color = "#200c0e" // Dark Blue 
        
        VAR Ring1_Main_Color = "url(#Ring_Outside)" //Using Ring 1 Outside gradiiant You can change to any hex value if you want.
        VAR Ring2_Main_Color = "url(#Ring_Middle)"  //Using Ring 2 Middle gradiiant. You can change to any hex value if you want.
        VAR Ring3_Main_Color = "url(#Ring_Inside)"  //Using Ring 3 Inside gradiant You can change to any hex value if you want.
        
        VAR Ring1_Speed = "2s" // Change ths speed ring 1 animation 
        VAR Ring2_Speed = "2s" // Change ths speed ring 2 animation 
        VAR Ring3_Speed = "2s" // Change ths speed ring 3 animation 
        
        
        // Ring Values 
        
        VAR Ring1_Goal = SUM('Ring Goal'[GoalCalories]) 
        VAR Ring2_Goal = SUM('Ring Goal'[GoalExercise]) 
        VAR Ring3_Goal = SUM('Ring Goal'[GoalStand]) 
        
        VAR Ring1_Value = SUM('Ring Data'[Calories]) 
        VAR Ring2_Value = SUM('Ring Data'[Exercise]) 
        VAR Ring3_Value = SUM('Ring Data'[Stand]) 
        
        VAR Ring1_Percentage = DIVIDE(Ring1_Value, Ring1_Goal) * 100 
        VAR Ring2_Percentage = DIVIDE(Ring2_Value, Ring2_Goal) * 100 
        VAR Ring3_Percentage = DIVIDE(Ring3_Value, Ring3_Goal) * 100 
        
        VAR Ring1_Offset_Value = IF(Ring1_Percentage >= 100, 0, 100 - Ring1_Percentage )
        VAR Ring2_Offset_value = IF(Ring2_Percentage >= 100, 0, 100 - Ring2_Percentage )
        VAR Ring3_Offset_Value = IF(Ring3_Percentage >= 100, 0, 100 - Ring3_Percentage )
        
        
        
        
         VAR FinalCode= "<?xml version='1.0' encoding='UTF-8'?>
        <svg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 273.22 273.22'>
          <defs>
            <style>
          
              .cls-1, .cls-2, .cls-3, .cls-4, .cls-5, .cls-6 {
                fill: " & Circle_BG & ";  /* Set the Circle background to transparent */
                stroke-miterlimit: 10;
                stroke-width: " & Rings_Width & "px;  /* Set the stroke width of each ring */
              }
            
                  .cls-1 { stroke: " & Ring1_BG_Color & "; }
                  .cls-2 { stroke: " & Ring3_BG_Color & "; } 
                  .cls-3 { stroke: " & Ring2_BG_Color & "; }
                  .cls-4 { stroke: " & Ring2_Main_Color & "; }
                  .cls-4, .cls-5, .cls-6 { stroke-linecap: round; }
                  .cls-5 { stroke: " & Ring1_Main_Color & ";}
                  .cls-6 { stroke: " & Ring3_Main_Color & ";}
        
        
        
              #Ring1Outer {
                         stroke-dasharray: 100; 
                         stroke-dashoffset: 100;
                         animation: aniRing1Outer " & Ring1_Speed & " ease-in-out forwards;
                         
        
                } 
        
        
                @keyframes aniRing1Outer
                {
                    from{ stroke-dashoffset: 100px; }
                     to { stroke-dashoffset: " & Ring1_Offset_Value & "px; }
                }
        
        
        
        
              #Ring2Middle {
                         stroke-dasharray: 100; 
                         stroke-dashoffset: 100;
                         animation: aniRing2Middle " & Ring2_Speed & " ease-in-out forwards;
        
                } 
        
        
                @keyframes aniRing2Middle
                {
                    from{ stroke-dashoffset: 100px; }
                    to { stroke-dashoffset: " & Ring2_Offset_value & "px; }
                }
        
        
                 #Ring3Inner {
                         stroke-dasharray: 100; 
                         stroke-dashoffset: 100;
                         animation: aniRing3Inner " & Ring3_Speed & " ease-in-out forwards;
        
                } 
        
                @keyframes aniRing3Inner
                {
                    from{ stroke-dashoffset: 100px; }
                    to { stroke-dashoffset: " & Ring3_Offset_Value & "px; }
                }
        
        
            </style>
        
            <linearGradient id='Ring_Outside' data-name='Ring 1 Outside' x1='0' y1='136.61' x2='273.22' y2='136.61' gradientUnits='userSpaceOnUse'>
              <stop offset='0' stop-color='#cf2d32'/>
              <stop offset='1' stop-color='#ea4785'/>
            </linearGradient>
            <linearGradient id='Ring_Middle' data-name='Ring 2 Middle' x1='41.73' y1='136.61' x2='231.49' y2='136.61' gradientUnits='userSpaceOnUse'>
              <stop offset='0' stop-color='#6f9935'/>
              <stop offset='1' stop-color='#b4d44e'/>
            </linearGradient>
            <linearGradient id='Ring_Inside' data-name='Ring 3 Outer' x1='83.39' y1='136.6' x2='189.81' y2='136.6' gradientUnits='userSpaceOnUse'>
              <stop offset='0' stop-color='#48aab8'/>
              <stop offset='1' stop-color='#8bd0c1'/>
            </linearGradient>
        
        
        
          </defs>
        
        <!-- Background Rings --> 
           <circle  class='cls-1' cx='136.6' cy='136.6' r='33.21'/>  
           <circle class='cls-2' cx='136.61' cy='136.61' r='116.61'/>  
           <circle class='cls-3' cx='136.61' cy='136.61' r='74.88'/>
         
        <!-- Animated Rings --> 
           <path id ='Ring1Outer' pathLength='100' class='cls-5' d='M138.21,20.01c63.67.86,115.01,52.73,115.01,116.6s-52.21,116.61-116.61,116.61S20,201.01,20,136.61,72.21,20,136.61,20h1.6' /> 
           <path id ='Ring2Middle' pathLength='100' class='cls-4' d='M138.23,61.75c40.61.86,73.26,34.05,73.26,74.86s-33.53,74.88-74.88,74.88-74.88-33.53-74.88-74.88,33.53-74.88,74.88-74.88c.54,0,1.08,0,1.62.02'/> 
           <path id ='Ring3Inner' pathLength='100' class='cls-6' d='M138.58,103.45c17.42,1.03,31.23,15.48,31.23,33.15s-14.87,33.21-33.21,33.21-33.21-14.87-33.21-33.21,14.87-33.21,33.21-33.21c.67,0,1.33.02,1.98.06'/> 
        
        
        
        </svg>"
        
        RETURN FinalCode