You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

Build a display control once in C# with the Portable API and use it in both the Windows clients and the browser: one source file, compiled into a WPF build and an HTML5 build.

Reference → Code → Extensions API → Custom Controls → Portable API Reference


A portable control is a WPF UserControl that implements T.Toolkit.IPortableControl and is compiled twice from the same source: once for the WPF clients and once, through OpenSilver, for the HTML5 (browser) client. After you copy the two DLLs into the FrameworX installation, the Designer lists the control in the Components panel like a built-in control, with its own configuration panel.

This page covers building the control, deploying it to FrameworX and finding it in the Designer, using a downloadable template.

A portable control is not placed with the WPF Control element. For a control that only needs to run on WPF clients, see WPF Control API Reference. For one that only runs in the browser, see Blazor Control API Reference.


How a portable control works

One control source file is linked into two projects. Each project produces a DLL with the same file name, and each DLL goes to its own folder of the FrameworX installation:

WPF build

HTML5 build

Project

SampleGauge\TSampleGauge.csproj

SampleGauge.HTML5\TSampleGauge.HTML5.csproj

Target

.NET Framework 4.8

.NET Standard 2.0 + OpenSilver

User interface

WPF components (the template uses the Syncfusion WPF gauge shipped with FrameworX)

JavaScript components driven from C# through a wrapper class (the template uses the Syncfusion JavaScript gauge from the web client's Libraries folder)

Output

T.Portable.Controls.TSampleGauge.dll

T.Portable.Controls.TSampleGauge.dll

Deploy to

C:\Program Files\Tatsoft\FrameworX\fx-10\WpfControls\

C:\Program Files\Tatsoft\FrameworX\fx-10\HTML5\ExtensionControls\

Used by

The Designer, the RichClient and the SmartClient

The HTML5 client in the browser, which downloads it by name

There is no registration step. When the Designer starts, it scans the WpfControls folder and lists every control it finds there. If a DLL with the same name is also in HTML5\ExtensionControls, the control is portable; if not, it is listed with a (WPF) suffix and works only on WPF clients.


Download the template

Download PortableControl-SampleGauge.zip. It contains a complete portable control, TSampleGauge: a circular gauge linked to a tag, with a minimum, a maximum, a header and a Designer configuration panel.

File

Compiled into

Role

SampleGauge\TSampleGauge.cs

Both (linked)

The control: properties, user interface, runtime tag binding and the IPortableControl members.

SampleGauge\TSampleGaugeConfig.xaml / .xaml.cs

WPF only

The configuration panel the Designer opens when you double-click the control.

SampleGauge\TSampleGauge.png

WPF only

The icon shown in the Components panel.

SampleGauge.HTML5\SfCircularGauge.cs

HTML5 only

The wrapper: a C# class with the same members as the WPF gauge, implemented on the JavaScript gauge.

nuget.config

Adds the OpenSilver package feed the HTML5 project needs.

SampleGauge.sln

Opens both projects.

Validated with FrameworX 10.1.5, in the WPF client and in the browser.


Requirements

  • Windows and Visual Studio with the .NET desktop development workload, and a current .NET SDK (the template was built with .NET SDK 10).

  • A FrameworX installation. Both projects reference the FrameworX assemblies (T.Library, T.Toolkit and, for WPF, T.Library.Wpf, T.Toolkit.Wpf, T.Kernel, T.Wpf and the Syncfusion gauge) from C:\Program Files\Tatsoft\FrameworX\fx-10\.

  • Access to nuget.org and to the OpenSilver package feed https://www.myget.org/F/opensilver/api/v3/index.json, declared in the template's nuget.config.

  • Place the control on a Portable display to use it in the WPF clients (Windows, RichClient, SmartClient) and in the browser.


Rules

Follow these rules for every portable control. Breaking one usually produces no error: the control is missing from the Components panel, shows as (WPF), or does not load in the browser.

  1. Name the assembly T.Portable.Controls.T<Name>, with RootNamespace T.Portable.Controls. The Designer only loads DLLs from WpfControls whose file name contains T.Portable..

  2. Both builds have the same file name. The WPF build goes to fx-10\WpfControls\, the HTML5 build to fx-10\HTML5\ExtensionControls\. The browser downloads the HTML5 build by that name.

  3. The control class is public and implements IPortableControl (namespace T.Toolkit). The Designer registers every public class in the DLL that implements it.

  4. The static GetIconImage() returns the icon. Its pack:// URI must name your own assembly and .png file. If it returns nothing, the Designer skips the control without an error.

  5. The class name decides where the control appears. The Components panel label is the class name without the leading T. The section is chosen by a case-sensitive text match on the class name: Chart goes to Charts, Gauge to Gauges, View to Viewer; any other name goes to Extensions.

  6. Do not reuse a product control name. Your DLL would replace the product's. Four retired names are also never listed in the Components panel: TCircularGauge, TCenterValueCircularGauge, TRangeCircularGauge and TSemiCircleCircularGauge.

  7. The control source is linked into both projects, never copied. Put platform differences behind #if OPENSILVER. The configuration panel and the icon belong to the WPF project only.

  8. The OpenSilver package version matches the FrameworX web client. For FrameworX 10.1.5 it is 3.4.0-preview-2026-07-03-134452-d65dd77f, with System.Formats.Asn1 and System.Security.Cryptography.Xml 10.0.10. A different version builds, but the HTML5 build fails to load in the browser.

  9. FrameworX assemblies are referenced, not copied. Every FrameworX and third-party reference has <Private>False</Private>, and you deploy only the control DLL from each output folder.

  10. Close the Designer before replacing the DLLs, and restart it to see a new control. The Designer keeps the DLLs loaded, and it scans WpfControls only when it starts.


Build, deploy and use the template

1. Build

Open SampleGauge.sln and build in Release. If FrameworX is not installed in C:\Program Files\Tatsoft\FrameworX, change FrameworXPath in both project files to your installation's fx-10 folder, keeping the trailing backslash. The two builds are:

  • WPF: SampleGauge\bin\Release\T.Portable.Controls.TSampleGauge.dll

  • HTML5: SampleGauge.HTML5\bin\T.Portable.Controls.TSampleGauge.dll

Both output folders also contain other assemblies. Copy only the control DLL (rule 9).

2. Deploy to FrameworX

  1. Close the Designer (rule 10).

  2. Copy the WPF build to C:\Program Files\Tatsoft\FrameworX\fx-10\WpfControls\.

  3. Copy the HTML5 build to C:\Program Files\Tatsoft\FrameworX\fx-10\HTML5\ExtensionControls\.

Both folders are under Program Files, so copying may require administrator rights.

3. Find it in the Designer

Open the Designer and a Portable display in Displays / Draw. In the Components panel, open the Gauges section: the control is listed as SampleGauge, without a (WPF) suffix. A control whose class name contains none of Chart, Gauge or View is listed in an Extensions section instead (rule 5).

4. Place it and link a tag

Drag SampleGauge onto the display and double-click it to open its configuration panel. Enter a tag in Linked Value (for example Tag.Level) and, if needed, the Minimum Value, Maximum Value and Header. Run the solution: the gauge follows the tag in the WPF client and in the browser.

5. Make it your own control

Choose a class name that follows rules 1, 5 and 6, then change it everywhere below. SfCircularGauge is the name of the JavaScript gauge wrapper, not of the control, and can stay.

File

What to change

File and folder names

TSampleGauge.*, TSampleGaugeConfig.*, and the SampleGauge / SampleGauge.HTML5 folders.

TSampleGauge.csproj and TSampleGauge.HTML5.csproj

AssemblyName, the Compile, Page and Resource items, and the linked source path in the HTML5 project.

TSampleGauge.cs

The class and constructor names, the configuration class in GetConfigControl(), and the assembly and file name in the pack:// URI inside GetIconImage().

TSampleGaugeConfig.xaml / .xaml.cs

x:Class, the class name, the control type it casts to, and the design-time DataContext type.

Properties\AssemblyInfo.cs, SampleGauge.sln

The title, product and project names.


How the code works

One source, two platforms

The shared file switches between the WPF gauge and the JavaScript wrapper with a conditional using. The OpenSilver package defines the OPENSILVER symbol in the HTML5 project.

TSampleGauge.cs
#if OPENSILVER
using T.Portable.Components.Gauges;
#else
using Syncfusion.UI.Xaml.Gauges;
#endif

Linking a tag

Every bindable property is a string that holds a link: a constant, a tag or an expression. The setter applies constants immediately so the Designer preview updates. Tag and expression values arrive at runtime.

TSampleGauge.cs: a bindable property
public ObjectReference linkedValueRun = null;
private string _linkedValue = "";
[DefaultValue("")]
public string LinkedValue
{
    get { return _linkedValue; }
    set
    {
        if (this._linkedValue != value)
        {
            this._linkedValue = value;
            NotifyDesignPropertyChanged();
            if (IsStaticValue(value))
            {
                double x = TConvert.ToDouble(value);
                gPointer.Value = x;
                if (gPointer2 != null)
                {
                    gPointer2.Value = x;
                }
            }
        }
    }
}

StartRuntime() runs when the display opens. It turns each link into an ObjectReference and registers HandleTagEvent, which runs on every change and pushes the new value into the gauge. Dispose() releases each reference with WK.Dispose when the display closes.

TSampleGauge.cs: StartRuntime (start and end)
public void StartRuntime()
{
    UpdateColors();

    this.headerRun = ObjectReference.ParseString(WK.UntokenizeObjectName(this.HeaderLink).Trim('"'));
    if (!WK.IsUpdatingReport && this.headerRun != null)
        this.headerRun.RegisterEvent(this.HandleTagEvent);

    this.linkedValueRun = ObjectReference.ParseExpression(this.LinkedValue.Trim(), null);
    if (!WK.IsUpdatingReport && this.linkedValueRun != null)
        this.linkedValueRun.RegisterEvent(this.HandleTagEvent);

    ObjectReference.AddEventToExecute(this.HandleTagEvent);
}
TSampleGauge.cs: inside HandleTagEvent
if (this.linkedValueRun != null)
    gScale.Pointers[0].Value = TK.To<double>(await this.linkedValueRun.GetValueAsync());

Symbols, copy and paste, and saving

Every link must also be listed in the token, label, clipboard and save members, one line per link. Leaving them empty compiles and seems to work, then breaks symbol parameters, renames, copy and paste, and cross references.

TSampleGauge.cs: GetTokens and OnSave
public void GetTokens(object symbol)
{
    WK.GetTokens(symbol, MinValueLink, eFieldTypeTk.NumberOrObject);
    WK.GetTokens(symbol, MaxValueLink, eFieldTypeTk.NumberOrObject);
    WK.GetTokens(symbol, LinkedValue, eFieldTypeTk.Expression);
    WK.GetTokens(symbol, HeaderLink, eFieldTypeTk.ObjectOrString);
    WK.GetTokens(symbol, RimThicknessLink, eFieldTypeTk.NumberOrObject);
    WK.GetTokens(symbol, PointerThicknessLink, eFieldTypeTk.NumberOrObject);
    WK.GetTokens(symbol, MajorTicksQuantityLink, eFieldTypeTk.NumberOrObject);
    WK.GetTokens(symbol, MinorTicksPerIntervalLink, eFieldTypeTk.NumberOrObject);
}

public void OnSave(int displayId, object el)
{
    OnStartClipboardCopy();
    OnFinishClipboardCopy();

    WK.SaveHelper.SaveStart(displayId, el);

    WK.SaveHelper.SaveLink(LinkedValue, "LinkedValue");
    WK.SaveHelper.SaveLink(MinValueLink, "MinValue");
    WK.SaveHelper.SaveLink(MaxValueLink, "MaxValue");
    WK.SaveHelper.SaveLink(HeaderLink, "HeaderLink");
    WK.SaveHelper.SaveLink(RimThicknessLink, "RimThickness");
    WK.SaveHelper.SaveLink(PointerThicknessLink, "PointerThickness");
    WK.SaveHelper.SaveLink(MajorTicksQuantityLink, "MajorTicksQuantity");
    WK.SaveHelper.SaveLink(MinorTicksPerIntervalLink, "MinorTicksPerInterval");

Icon and configuration panel

TSampleGauge.cs
        static public byte[] GetIconImage()
        {
            string _imageUri = @"pack://application:,,,/T.Portable.Controls.TSampleGauge;component/TSampleGauge.png";
            return WK.GetImageFromUri(_imageUri);
        }

        public IControlConfig GetConfigControl()
        {
#if OPENSILVER
            return null;
#else
            return new TSampleGaugeConfig();
#endif
        }

The browser side

The HTML5 build declares the JavaScript and CSS files the web client loads before the control renders, through IHTML5ControlDependencies. Paths are relative to the web client root or absolute URLs.

TSampleGauge.cs: IHTML5ControlDependencies
public string[] ControlDataJS
{
    get
    {
        return new string[] {
                @"Libraries/ej2-base/dist/global/ej2-base.min.js",
                @"Libraries/ej2-circulargauge/dist/global/ej2-circulargauge.min.js",
            };
    }
}

public string[] ControlDataCSS
{
    get
    {
        return new string[] {
                "Libraries/ej2-popups/styles/material.css",
                "https://fonts.googleapis.com/css?family=Roboto:400,500"
           };
    }
}

The wrapper exposes the same members as the WPF gauge. Each property that affects rendering must push its value to the JavaScript object in its setter. A property that is only stored in C# compiles, but a tag change never reaches the browser.

SfCircularGauge.cs: a property that pushes to JavaScript
public double Value
{
    get => objValue;
    set 
    {
        objValue = value;
        if (this.CircularGaugeWebAssemblyObject == null || this.circularPointer == null)
            return;

        OpenSilver.Interop.ExecuteJavaScript("$0.value = $1", this.circularPointer, objValue);
    }

The IPortableControl members

Member

When FrameworX calls it

What the control does

GetConfigControl()

The Designer opens the configuration panel

Returns the configuration panel on WPF, null under OPENSILVER.

StartRuntime()

The display opens at runtime

Turns links into ObjectReference objects and registers the change handler.

Dispose()

The display closes

Releases every ObjectReference and event it attached.

GetTokens, ApplyTokens

The control is used in a symbol

Exposes every link so symbol parameters are replaced.

GetLabels, ResolveLabels

Symbol labels are renamed

Same links, so renames reach the control.

GetStrings, ApplyStrings

Translation

Exposes fixed texts for localization.

OnStartClipboardCopy, OnFinishClipboardCopy, OnFinishClipboardPaste

Copy and paste

Keeps every link valid, one line per link.

OnSave(int displayId, object el)

The display is saved

Calls WK.SaveHelper.SaveStart, then SaveLink for each link. This feeds cross references.

GetIconImage()

The Designer lists the control

Static method, not part of the interface. Returns the icon.

Optional interfaces used by the template: IHTML5ControlDependencies (JavaScript and CSS for the browser), IWKUpdateColors (theme changes) and INotifyPropertyChanged (refreshes the Designer preview from the configuration panel).


Troubleshooting

Symptom

Cause

Fix

The control is not in the Components panel.

The DLL is not in WpfControls, its name does not contain T.Portable., the Designer was not restarted, GetIconImage() returns nothing, or the class uses a retired name.

Check rules 1, 3, 4, 6 and 10.

The control appears in a different section than expected.

The section comes from the class name, with a case-sensitive match.

See rule 5.

The control shows (WPF).

No DLL with the same name in HTML5\ExtensionControls.

Deploy the HTML5 build with the exact same file name (rule 2).

It works in the WPF client but not in the browser.

OpenSilver version mismatch, a missing JavaScript or CSS dependency, or a wrapper property that does not push to JavaScript.

Check rule 8, IHTML5ControlDependencies, and the wrapper setters.

Restore fails: package not found.

The OpenSilver preview packages are not on nuget.org.

Keep the template's nuget.config with the OpenSilver feed.

Build error NU1605, package downgrade of System.Security.Cryptography.Xml.

An older version is referenced than the one OpenSilver requires.

Use version 10.0.10 (rule 8).

The WPF output folder is full of FrameworX DLLs.

A reference lacks <Private>False</Private>.

Set it on every FrameworX reference, and deploy only the control DLL (rule 9).

Copying fails: "being used by another process".

The Designer or a client has the DLL loaded.

Close them and copy again (rule 10).


Complete source

The project files and the full template, file by file. The same files are in PortableControl-SampleGauge.zip.

SampleGauge/TSampleGauge.csproj
<?xml version="1.0" encoding="utf-8"?>
<!-- WPF half of the portable control. Deploy the output DLL to <FrameworX>\fx-10\WpfControls\ -->
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{80A4DE71-2304-4AD4-96DA-0407245FAEBC}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <!-- Keep RootNamespace = T.Portable.Controls and AssemblyName = T.Portable.Controls.T<Name>:
         the Designer only loads DLLs whose name contains "T.Portable." -->
    <RootNamespace>T.Portable.Controls</RootNamespace>
    <AssemblyName>T.Portable.Controls.TSampleGauge</AssemblyName>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <!-- FrameworX installation folder that holds the reference assemblies. Keep the trailing backslash. -->
    <FrameworXPath Condition=" '$(FrameworXPath)' == '' ">C:\Program Files\Tatsoft\FrameworX\fx-10\</FrameworXPath>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Data" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xaml" />
    <Reference Include="System.Xml" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="WindowsBase" />
    <!-- FrameworX and third-party assemblies: referenced from the installation, never copied (Private=False). -->
    <Reference Include="Microsoft.CSharp">
      <HintPath>$(FrameworXPath)Microsoft.CSharp.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System.Text.Json">
      <HintPath>$(FrameworXPath)System.Text.Json.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Syncfusion.Licensing">
      <HintPath>$(FrameworXPath)Syncfusion.Licensing.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Syncfusion.SfGauge.WPF">
      <HintPath>$(FrameworXPath)Syncfusion.SfGauge.WPF.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Kernel">
      <HintPath>$(FrameworXPath)T.Kernel.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Library">
      <HintPath>$(FrameworXPath)T.Library.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Library.Wpf">
      <HintPath>$(FrameworXPath)T.Library.Wpf.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Toolkit">
      <HintPath>$(FrameworXPath)T.Toolkit.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Toolkit.Wpf">
      <HintPath>$(FrameworXPath)T.Toolkit.Wpf.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Wpf">
      <HintPath>$(FrameworXPath)T.Wpf.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="TSampleGauge.cs" />
    <Compile Include="TSampleGaugeConfig.xaml.cs">
      <DependentUpon>TSampleGaugeConfig.xaml</DependentUpon>
    </Compile>
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <Page Include="TSampleGaugeConfig.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </ItemGroup>
  <ItemGroup>
    <Resource Include="TSampleGauge.png" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
SampleGauge.HTML5/TSampleGauge.HTML5.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <!-- HTML5 half of the portable control. Deploy the output DLL (same file name as the WPF half)
       to <FrameworX>\fx-10\HTML5\ExtensionControls\ -->
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
    <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
    <OutputPath>bin\</OutputPath>
    <RootNamespace>T.Portable.Controls</RootNamespace>
    <AssemblyName>T.Portable.Controls.TSampleGauge</AssemblyName>
    <NoWarn>1701;1702;MSB3277</NoWarn>
    <DebugType>embedded</DebugType>
    <!-- FrameworX installation folder that holds the reference assemblies. Keep the trailing backslash. -->
    <FrameworXPath Condition=" '$(FrameworXPath)' == '' ">C:\Program Files\Tatsoft\FrameworX\fx-10\</FrameworXPath>
  </PropertyGroup>

  <!-- The shared control source is LINKED from the WPF project, never copied. -->
  <ItemGroup>
    <Compile Include="..\SampleGauge\TSampleGauge.cs" Link="TSampleGauge.cs" />
  </ItemGroup>

  <!-- The OpenSilver version must match the FrameworX web client exactly (FrameworX 10.1.5 below).
       Preview builds come from the OpenSilver MyGet feed declared in nuget.config. -->
  <ItemGroup>
    <PackageReference Include="OpenSilver" Version="3.4.0-preview-2026-07-03-134452-d65dd77f">
      <PrivateAssets>All</PrivateAssets>
    </PackageReference>
    <PackageReference Include="System.Formats.Asn1" Version="10.0.10" />
    <PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.10" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="T.Library">
      <HintPath>$(FrameworXPath)T.Library.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="T.Toolkit">
      <HintPath>$(FrameworXPath)T.Toolkit.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

</Project>
nuget.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="OpenSilver MyGet" value="https://www.myget.org/F/opensilver/api/v3/index.json" />
  </packageSources>
</configuration>
SampleGauge/TSampleGauge.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Threading.Tasks;

#if OPENSILVER
using T.Portable.Components.Gauges;
#else
using Syncfusion.UI.Xaml.Gauges;
#endif

using T.Library;
using T.Toolkit;
using T.Toolkit.Wpf;
using System.Collections;
using System.Xml.Serialization;

namespace T.Portable.Controls
{
    public class TSampleGauge : UserControl, IPortableControl, INotifyPropertyChanged, IHTML5ControlDependencies, IWKUpdateColors
    {

        #region INotifyPropertyChanged Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyDesignPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (WK.IsDesignMode)
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region Private Fields
        private readonly TextBlock gHeader = null;
        private readonly SfCircularGauge gauge = null;
        private readonly CircularScale gScale = null;
        private readonly CircularPointer gPointer = null;
        private readonly CircularPointer gPointer2 = null;

        private int labelsCreated = 0;
        private double majorTicksQuantityDouble = 11;

        private Grid mainGrid = null;
        #endregion

        #region Properties

        #region SampleValue Property
        private string _sampleValue = "";
        [DefaultValue("")]
        [XmlIgnore]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string SampleValue
        {
            get => _sampleValue;
            set
            {
                if (_sampleValue != value)
                {
                    _sampleValue = value;
                    UpdateDesignUI();
                }
            }
        }
        #endregion

        #region Control Access Properties

        public SfCircularGauge Gauge { get { return gauge; } }
        public CircularScale Scale { get { return gScale; } }
        public CircularPointer Pointer { get { return gPointer; } }

        #endregion 

        #region DataEntry Validation Properties
#if !OPENSILVER
        public EditInfo NumberOrObjectField
        {
            get => EditInfo.FactoryNumberOrObject;
        }

        public EditInfo StringWithExpressionsField
        {
            get => EditInfo.FactoryStringWithExpressions;
        }

        public EditInfo ObjectOrStringField
        {
            get => EditInfo.FactoryObjectOrString;
        }
        public EditInfo ExpressionField
        {
            get => EditInfo.FactoryExpression;
        }
#endif
        #endregion

        #region Control Properties

        #region Header Properties

        public ObjectReference headerRun = null;
        private string _headerLink = "";
        [DefaultValue("")]
        public string HeaderLink
        {
            get { return this._headerLink; }
            set
            {
                if (this._headerLink != value)
                {
                    this._headerLink = value;
                    NotifyDesignPropertyChanged();

                    string str = WK.UntokenizeObjectName(value);
                    if (this.gHeader != null)
                        gHeader.Text = str;
                }
            }
        }
        public string GaugeTitle { set { HeaderLink = value; } }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string LabelLink
        {
            get => this._headerLink.Contains("@") ? $"{_headerLink}" : _headerLink;
            set
            {
                if (value == null)
                    return;
                value = value.replace("{}", "");
                if (!value.Contains("{"))
                {
                    _headerLink = value;
                    return;
                }
                int index = value.IndexOf("{");
                if (index >= 0)
                {
                    _headerLink = value.Substring(index + 1);
                    index = value.IndexOf("}");
                    if (index >= 0)
                        _headerLink += value.Substring(0, index);
                }

            }
        }

        public bool HasDynamicHeader { get => _headerLink.Contains("@") || _headerLink.Contains("{"); }
        public bool HasStaticHeader { get => !HasDynamicHeader && !String.IsNullOrWhiteSpace(_headerLink); }

        [DefaultValue(12.0)]
        public double HeaderFontSize
        {
            get { return this.gHeader.FontSize; }
            set
            {
                if (this.gHeader.FontSize != value)
                {
                    this.gHeader.FontSize = value;
                    NotifyDesignPropertyChanged();
                }
            }
        }
        public double FontSizeTitle { set { HeaderFontSize = value; } }

        public double[] HeaderFontSizes
        {
            get { return new double[] { 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 32, 36, 48, 64, 72, 108, 144 }; }
        }

        public TK.eTkHeaderAlignment[] HeaderAlignments
        {
            get { return (TK.eTkHeaderAlignment[])Enum.GetValues(typeof(TK.eTkHeaderAlignment)); }
        }

        private TK.eTkHeaderAlignment _headerAlignment = TK.eTkHeaderAlignment.Bottom;
        [DefaultValue(TK.eTkHeaderAlignment.Bottom)]
        public TK.eTkHeaderAlignment HeaderAlignment
        {
            get { return _headerAlignment; }
            set
            {
                if (_headerAlignment != value)
                {
                    _headerAlignment = value;
                    NotifyDesignPropertyChanged();
                    this.UpdateGridContent();
                }
            }
        }

        #region Localize Property

        private bool _localize = true;
        [DefaultValue(false)]
        public bool Localize
        {
            get { return this._localize; }
            set
            {
                if (this._localize != value)
                {
                    this._localize = value;
                    this.NotifyDesignPropertyChanged(nameof(Localize));
                }
            }
        }

        #endregion

        #endregion 

        #region LinkedValue and Range Properties

        public ObjectReference linkedValueRun = null;
        private string _linkedValue = "";
        [DefaultValue("")]
        public string LinkedValue
        {
            get { return _linkedValue; }
            set
            {
                if (this._linkedValue != value)
                {
                    this._linkedValue = value;
                    NotifyDesignPropertyChanged();
                    if (IsStaticValue(value))
                    {
                        double x = TConvert.ToDouble(value);
                        gPointer.Value = x;
                        if (gPointer2 != null)
                        {
                            gPointer2.Value = x;
                        }
                    }
                }
            }
        }
        public string Value { set { LinkedValue = value; } }

        public ObjectReference minValueRun = null;
        private string _minValueLink = "0";
        [DefaultValue("0")]
        public string MinValueLink
        {
            get { return this._minValueLink; }
            set
            {
                if (this._minValueLink != value)
                {
                    this._minValueLink = value;
                    NotifyDesignPropertyChanged();
                    if (IsStaticValue(value))
                    {
                        double x = TConvert.ToDouble(value);
                        gScale.StartValue = x;

                        double interval = (gScale.EndValue - gScale.StartValue) / (this.majorTicksQuantityDouble - 1);
                        this.gScale.Interval = RoundDown(interval, 3);
                    }

                    this.labelsCreated = 0;
#if OPENSILVER
                    this.gauge.LabelsCreated = 0;
#endif
                }
            }
        }
        public string StartValue { set { _minValueLink = value; } }
        public string RangeStartLink { set { _minValueLink = value; } }


        public ObjectReference maxValueRun = null;
        private string _maxValueLink = "100";
        [DefaultValue("100")]
        public string MaxValueLink
        {
            get { return this._maxValueLink; }
            set
            {
                if (this._maxValueLink != value)
                {
                    this._maxValueLink = value;
                    NotifyDesignPropertyChanged();
                    if (IsStaticValue(value))
                    {
                        double x = TConvert.ToDouble(value);
                        if (x != 0)
                        {
                            gScale.EndValue = x;
                            double pointerVal = (TConvert.ToDouble(MinValueLink) + TConvert.ToDouble(MaxValueLink)) * 0.5;
                            gPointer.Value = pointerVal;
                            if (gPointer2 != null)
                            {
                                gPointer2.Value = pointerVal;
                            }
                        }
                        double interval = (gScale.EndValue - gScale.StartValue) / (this.majorTicksQuantityDouble - 1);
                        this.gScale.Interval = RoundDown(interval, 3);
                    }

                    this.labelsCreated = 0;
#if OPENSILVER
                    this.gauge.LabelsCreated = 0;
#endif
                }
            }
        }
        public string EndValue { set { _maxValueLink = value; } }
        public string RangeEndLink { set { _maxValueLink = value; } }

#endregion

        #region Pointer Properties


        [DefaultValue(PointerType.RangePointer)]
        public PointerType PointerType
        {
            get
            {
                if (this.gPointer == null)
                    return PointerType.RangePointer;
                return this.gPointer.PointerType;
            }
            set
            {
                if (this.gPointer == null)
                    return;
                if (this.gPointer.PointerType != value)
                {
                    this.gPointer.PointerType = value;
                    if (value == PointerType.NeedlePointer || !_showNeedlePointer)
                    {
                        if (gScale.Pointers.Contains(this.gPointer2))
                            gScale.Pointers.Remove(this.gPointer2);
                    }
                    else if (_showNeedlePointer)
                    {
                        if (!gScale.Pointers.Contains(this.gPointer2))
                            gScale.Pointers.Add(this.gPointer2);
                    }
                    NotifyDesignPropertyChanged();
                    NotifyDesignPropertyChanged(nameof(CanSecondPointer));
                }
            }
        }
        public int Pointer_Type { set { PointerType = TConvert.To<PointerType>(value); } }


        public bool CanSecondPointer => PointerType != PointerType.NeedlePointer;
        private bool _showNeedlePointer = true;
        [DefaultValue(true)]
        public bool ShowNeedlePointer
        {
            get => _showNeedlePointer;
            set
            {
                if (_showNeedlePointer != value)
                {
                    _showNeedlePointer = value;
                    if (PointerType == PointerType.NeedlePointer || !_showNeedlePointer)
                    {
                        if (gScale.Pointers.Contains(this.gPointer2))
                            gScale.Pointers.Remove(this.gPointer2);
                    }
                    else
                    {
                            if (_showNeedlePointer &&!gScale.Pointers.Contains(this.gPointer2))
                                gScale.Pointers.Add(this.gPointer2);                        
                    }
                    NotifyDesignPropertyChanged();
                }
            }
        }

        public ObjectReference pointerThicknessRun = null;
        private string _pointerThicknessLink = "10";
        [DefaultValue("10")]
        public string PointerThicknessLink
        {
            get { return this._pointerThicknessLink; }
            set
            {
                if (this._pointerThicknessLink != value)
                {
                    this._pointerThicknessLink = value;
                    NotifyDesignPropertyChanged();
                  
                    double x = TConvert.ToDouble(value);
                    if (x < 0)
                        return;
                    gPointer.RangePointerStrokeThickness = x;
                    gPointer.NeedlePointerStrokeThickness = x;         
                    if (gPointer2 != null)
                    {
                        gPointer2.RangePointerStrokeThickness = x;
                        gPointer2.NeedlePointerStrokeThickness = x;
                    }
                }
            }
        }
        public string PointerThickness { set { PointerThicknessLink = value; } }

        private Color _pointerColor = Color.FromArgb(0xff, 0x6A, 0x5A, 0xCD);
        [DefaultValue(typeof(Color), "#FF6A5ACD")]
        public Color PointerColor
        {
            get => _pointerColor;
            set
            {
                if (_pointerColor != value)
                {
                    _pointerColor = value;
                    Brush stroke = PointerStroke;
                    this.gPointer.RangePointerStroke = stroke;
                    this.gPointer.SymbolPointerStroke = stroke;
                    this.gPointer.KnobStroke = stroke;
                    this.gPointer.KnobFill = stroke;
                    this.gPointer.NeedlePointerStroke = stroke;
                    if (gPointer2 != null)
                    {
                        this.gPointer2.RangePointerStroke = stroke;
                        this.gPointer2.SymbolPointerStroke = stroke;
                        this.gPointer2.KnobStroke = stroke;
                        this.gPointer2.KnobFill = stroke;
                        this.gPointer2.NeedlePointerStroke = stroke;
                    }


                    NotifyDesignPropertyChanged();
                    NotifyDesignPropertyChanged(nameof(PointerStroke));
                }
            }
        }

        [XmlIgnore]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Brush PointerStroke
        {
            get => new SolidColorBrush(_pointerColor);
            set => _pointerColor = (value is SolidColorBrush solid) ? solid.Color : _pointerColor;
        }

        #endregion

        #region Scale Properties

        [DefaultValue(true)]
        public bool ShowLabels
        {
            get
            {
                return this.gScale.ShowLabels;
            }
            set
            {
                if (this.gScale.ShowLabels != value)
                {
                    this.gScale.ShowLabels = value;
                    NotifyDesignPropertyChanged();
                }
            }
        }

        [DefaultValue(8.0)]
        public double ScaleFontSize
        {
            get { return this.gScale.FontSize; }
            set
            {
                if (this.gScale.FontSize != value)
                {
                    this.gScale.FontSize = value;
                    NotifyDesignPropertyChanged();
                }
            }
        }

        public double FontSizeLabel { set { ScaleFontSize = value; } }

        [DefaultValue(true)]
        public bool ShowTicks
        {
            get
            {
                return this.gScale.ShowTicks;
            }
            set
            {
                if (this.gScale.ShowTicks != value)
                {
                    this.gScale.ShowTicks = value;
                    NotifyDesignPropertyChanged();
                }
            }
        }

        public ObjectReference majorTicksQuantityRun = null;
        private string _majorTicksQuantityLink = "11";
        [DefaultValue("11")]
        public string MajorTicksQuantityLink
        {
            get { return this._majorTicksQuantityLink; }
            set
            {
                if (this._majorTicksQuantityLink != value)
                {
                    this._majorTicksQuantityLink = value;
                    NotifyDesignPropertyChanged();
                    if(this.gScale != null)
                    {
                        double start = this.gScale.StartValue;
                        double end = this.gScale.EndValue;

                        double tickQuantity = TConvert.ToDouble(value);
                        if (tickQuantity <= 2)
                            tickQuantity = 2;

                        this.majorTicksQuantityDouble = tickQuantity;
                        double interval = (end - start) / (tickQuantity - 1);
                        this.gScale.Interval = RoundDown(interval, 3);                    
                    }                    
                }
                this.labelsCreated = 0;

#if OPENSILVER
                this.Gauge.LabelsCreated = 0;
                this.Gauge.LabelsQuantity = TConvert.ToInt(this.majorTicksQuantityDouble);
#endif

            }
        }
        public string MajorTicksQuantity { set { MajorTicksQuantityLink = value; } }

        public ObjectReference minorTicksPerIntervalRun = null;
        private string _minorTicksPerIntervalLink = "1";
        [DefaultValue("1")]
        public string MinorTicksPerIntervalLink
        {
            get { return this._minorTicksPerIntervalLink; }
            set
            {
                if (this._minorTicksPerIntervalLink != value)
                {
                    this._minorTicksPerIntervalLink = value;
                    NotifyDesignPropertyChanged();
                    if (this.gScale != null)
                        this.gScale.MinorTicksPerInterval = TConvert.ToInt(value);
                }
            }
        }
        public string MinorTicksPerInterval { set { MinorTicksPerIntervalLink = value; } }

#endregion

        #region Rim Properties

        public ObjectReference rimThicknessRun = null;
        private string _rimThicknessLink = "20";
        [DefaultValue("20")]
        public string RimThicknessLink
        {
            get { return this._rimThicknessLink; }
            set
            {
                if (this._rimThicknessLink != value)
                {
                    this._rimThicknessLink = value;
                    NotifyDesignPropertyChanged();

                    double x = TConvert.ToDouble(value);
                    if (x < 0)
                        return;
                    gScale.RimStrokeThickness = x;                    
                }
            }
        }
        public string RimThickness {  set { RimThicknessLink = value; } }

        private Color _rimStrokeColor = Color.FromArgb(0xFF, 0xC1, 0xC1, 0xC1);
        [DefaultValue(typeof(Color), "#FFC1C1C1")]
        public Color RimStrokeColor
        {
            get => _rimStrokeColor;
            set
            {
                if (_rimStrokeColor != value)
                {
                    _rimStrokeColor = value;
                    gScale.RimStroke = RimStroke;
                    NotifyDesignPropertyChanged();
                    NotifyDesignPropertyChanged(nameof(RimStroke));
                }
            }
        }

        [XmlIgnore]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Brush RimStroke
        {
            get => new SolidColorBrush(_rimStrokeColor);
            set => _rimStrokeColor = (value as SolidColorBrush)?.Color ?? _rimStrokeColor;
        }
        public Brush RimBackground { set { RimStroke = value; } }

        #endregion

        #endregion Control Properties

        #endregion Properties

        #region Constructors and EndInit

        public TSampleGauge()
        {
            this.mainGrid = new Grid();
            this.MinHeight = this.MinWidth = 40;

            this.gHeader = new TextBlock
            {
                Text = WK.UntokenizeObjectName(TConvert.ToString(HeaderLink)),
                FontFamily = new FontFamily("Verdana"),
                FontSize = 12,
                Foreground = WK.GetBrush(WK.eThemeColor.TextForeground) as Brush,
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center,
            };
            this.gPointer = new CircularPointer
            {
                RangePointerStrokeThickness = TConvert.ToDouble(this.PointerThicknessLink ?? "20"),
                RangeCap = RangeCap.None,
                RangePointerStroke = this.PointerStroke,
                PointerType = this.PointerType,
                NeedlePointerType = NeedlePointerType.Triangle,
                NeedlePointerStroke = this.PointerStroke,
                Value = 20,
            };
            this.gPointer2 = new CircularPointer
            {
                RangePointerStrokeThickness = TConvert.ToDouble(this.PointerThicknessLink ?? "20"),
                RangeCap = RangeCap.None,
                RangePointerStroke = this.PointerStroke,
                PointerType =PointerType.NeedlePointer,
                NeedlePointerType = NeedlePointerType.Triangle,
                NeedlePointerStroke = this.PointerStroke,
                Value = 20,
            };
            this.gScale = new CircularScale
            {
                FontSize = 8,
                StartValue = 0,
                EndValue = 100,
                StartAngle = 135,
                SweepAngle = 270,
                RimStroke = this.RimStroke, 
                RimStrokeThickness = TConvert.ToDouble(this.RimThicknessLink ?? "20"),
                RangePointerPosition = RangePointerPosition.Custom,
                ShowLabels = true,
                LabelStroke = WK.GetBrush(WK.eThemeColor.TextForeground) as Brush,
                ShowTicks = true,
                MinorTicksPerInterval = 1,
                Pointers = { gPointer, gPointer2 },                
            };

            this.gauge = new SfCircularGauge
            {
                VerticalAlignment = VerticalAlignment.Stretch,
                HorizontalAlignment = HorizontalAlignment.Stretch,
              
                Scales = { gScale },
                MainScale = gScale,
            };

            this.majorTicksQuantityDouble = 11;
            double interval = (gScale.EndValue - gScale.StartValue) / (this.majorTicksQuantityDouble - 1);
            gScale.Interval = this.RoundDown(interval, 3);


#if OPENSILVER
             this.Gauge.LabelsQuantity = TConvert.ToInt(this.majorTicksQuantityDouble);

#else
            this.gScale.LabelCreated += GScale_LabelCreated;
            this.gScale.ShowFirstLabel = true;
            this.gScale.ShowLastLabel = true;
#endif

            this.UpdateGridContent();
            _linkedValue = "#Value:";
        }

        private bool endInitAlreadyRun = false;
        public override void EndInit()
        {
#if !OPENSILVER
            base.EndInit();
#endif
            if (endInitAlreadyRun)
                return;
            endInitAlreadyRun = true;

            WK.EndInit(this);

            if (WK.IsDesignMode)
                return;

            this.gauge.Loaded += Gauge_Loaded;
            this.SizeChanged += this.UserControl_SizeChanged;
            WK.RuntimeThemeChanged += WK_RuntimeThemeChanged;
        }

        private void WK_RuntimeThemeChanged(object sender, EventArgs e)
        {
            this.UpdateColors();
        }

        public double RoundDown(double value, double digits)
        {
            double factor = Math.Pow(10, digits);

            return Math.Truncate(value * factor) / factor;
        }
        
        public void UpdateDesignUI()
        {
            if (!WK.IsDesignMode)
                return;
            if (this.gScale.Pointers == null)
                return;
            if (this.gScale.Pointers.Count > 0)
                this.gScale.Pointers[0].Value = TConvert.ToDouble(_sampleValue);
            if (this.gScale.Pointers.Count > 1)
                this.gScale.Pointers[1].Value = TConvert.ToDouble(_sampleValue);
        }

#if !OPENSILVER
        public override bool ShouldSerializeContent()
        {
            return false;
        }
#endif

        #endregion Constructors and EndInit

        #region Event Handlers

        private void Gauge_Loaded(object sender, RoutedEventArgs e)
        {
            this.labelsCreated = 0;
            this.gauge.Background = this.Background;
#if OPENSILVER
            this.Gauge.LabelsCreated = 0;
            this.gauge.GaugeHeight = this.Height;
            this.gauge.GaugeWidth = this.Width;
#endif
        }

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
#if OPENSILVER
            this.gauge.Width = e.NewSize.Width;
            this.gauge.Height = e.NewSize.Height;
            this.gauge.GaugeHeight = this.Height;
            this.gauge.GaugeWidth = this.Width;

            this.gauge.UpdateAxesRadius();
#else
            this.gauge.Width = e.NewSize.Width - 20;
            this.gauge.Height = e.NewSize.Height - 30;
#endif
        }

#if !OPENSILVER
        private void GScale_LabelCreated(object sender, LabelCreatedEventArgs e)
        {
            this.labelsCreated += 1;
            e.LabelText = TConvert.ToString(Math.Round(TConvert.ToDouble(e.LabelText), 2));
            if (this.labelsCreated < this.majorTicksQuantityDouble)            
                return;
            
            double _endValue = 100;
            if (IsStaticValue(this.MaxValueLink))
            {
                _endValue = this.gScale.EndValue;
            }

            e.LabelText = TConvert.ToString(_endValue);
            this.labelsCreated = 0;
        }

#endif

        #endregion Event Handlers

        #region IHTML5ControlDependencies Implementation

        public string[] ControlDataJS
        {
            get
            {
                return new string[] {
                        @"Libraries/ej2-base/dist/global/ej2-base.min.js",
                        @"Libraries/ej2-circulargauge/dist/global/ej2-circulargauge.min.js",
                    };
            }
        }

        public string[] ControlDataCSS
        {
            get
            {
                return new string[] {
                        "Libraries/ej2-popups/styles/material.css",
                        "https://fonts.googleapis.com/css?family=Roboto:400,500"
                   };
            }
        }

        #endregion

        #region IPortableControl Implementation

        static public byte[] GetIconImage()
        {
            string _imageUri = @"pack://application:,,,/T.Portable.Controls.TSampleGauge;component/TSampleGauge.png";
            return WK.GetImageFromUri(_imageUri);
        }

        public IControlConfig GetConfigControl()
        {
#if OPENSILVER
            return null;
#else
            return new TSampleGaugeConfig();
#endif
        }

        public void UpdateColors()
        {
            if (gauge == null)
                return;

            Brush b = WK.GetBrush(WK.eThemeColor.TextForeground) as Brush;
            this.gHeader.Foreground = b;
            this.gScale.LabelStroke = b;

#if OPENSILVER
            this.gScale.UpdateColors();
#endif
        }

        /// <summary>
        /// Initialization method called when display opens and this control is loaded. This method is used to start the data bindings and events.
        /// </summary>
        public void StartRuntime()
        {
            UpdateColors();

            this.headerRun = ObjectReference.ParseString(WK.UntokenizeObjectName(this.HeaderLink).Trim('"'));
            if (!WK.IsUpdatingReport && this.headerRun != null)
                this.headerRun.RegisterEvent(this.HandleTagEvent);

            this.linkedValueRun = ObjectReference.ParseExpression(this.LinkedValue.Trim(), null);
            if (!WK.IsUpdatingReport && this.linkedValueRun != null)
                this.linkedValueRun.RegisterEvent(this.HandleTagEvent);

            this.minValueRun = ObjectReference.ParseExpression(this.MinValueLink.Trim(), null);
            if (!WK.IsUpdatingReport && this.minValueRun != null)
                this.minValueRun.RegisterEvent(this.HandleTagEvent);

            this.maxValueRun = ObjectReference.ParseExpression(this.MaxValueLink.Trim(), null);
            if (!WK.IsUpdatingReport && this.maxValueRun != null)
                this.maxValueRun.RegisterEvent(this.HandleTagEvent);

            this.rimThicknessRun = ObjectReference.ParseExpression(this.RimThicknessLink, null);
            if (!WK.IsUpdatingReport && this.rimThicknessRun != null)
                this.rimThicknessRun.RegisterEvent(this.HandleTagEvent);

            this.pointerThicknessRun = ObjectReference.ParseExpression(this.PointerThicknessLink, null);
            if (!WK.IsUpdatingReport && this.pointerThicknessRun != null)
                this.pointerThicknessRun.RegisterEvent(this.HandleTagEvent);

            this.majorTicksQuantityRun = ObjectReference.ParseExpression(this.MajorTicksQuantityLink, null);
            if (!WK.IsUpdatingReport && this.majorTicksQuantityRun != null)
                this.majorTicksQuantityRun.RegisterEvent(this.HandleTagEvent);

            this.minorTicksPerIntervalRun = ObjectReference.ParseExpression(this.MinorTicksPerIntervalLink, null);
            if (!WK.IsUpdatingReport && this.minorTicksPerIntervalRun != null)
                this.minorTicksPerIntervalRun.RegisterEvent(this.HandleTagEvent);



            ObjectReference.AddEventToExecute(this.HandleTagEvent);
        }

        /// <summary>
        /// Dispose control and unregister events
        /// </summary>
        public void Dispose()
        {
            WK.Dispose(ref this.headerRun);
            WK.Dispose(ref this.minValueRun);
            WK.Dispose(ref this.maxValueRun);
            WK.Dispose(ref this.linkedValueRun);
            WK.Dispose(ref this.rimThicknessRun);
            WK.Dispose(ref this.pointerThicknessRun);
            WK.Dispose(ref this.majorTicksQuantityRun);
            WK.Dispose(ref this.minorTicksPerIntervalRun);
        }

        /// <summary>
        /// Handle tag event to update control value
        /// </summary>
        public async Task HandleTagEvent()
        {
            try
            {
                if (this.headerRun != null && this.gHeader != null && this.gauge != null)
                {
                    gHeader.Text = TK.To<string>(await this.headerRun.GetValueAsync());
                }

                if (this.gScale == null)
                    return;

                if (this.minValueRun != null)
                    gScale.StartValue = TK.To<double>(await this.minValueRun.GetValueAsync());
                if (this.maxValueRun != null)
                    gScale.EndValue = TK.To<double>(await this.maxValueRun.GetValueAsync());

                if (gScale.StartValue > gScale.EndValue)
                {
                    double tmp = gScale.StartValue;
                    gScale.StartValue = gScale.EndValue;
                    gScale.EndValue = tmp;
                }

                if (gScale.StartValue == gScale.EndValue)
                    gScale.EndValue = gScale.StartValue + 1;

                if (this.majorTicksQuantityRun != null)
                {
                    double quantity = TK.To<double>(await this.majorTicksQuantityRun.GetValueAsync());
                    quantity = (quantity >= 15) ? 10 : quantity;
                    if (quantity <= 2)
                        quantity = 2;

                    this.majorTicksQuantityDouble = quantity;
#if OPENSILVER
                    this.Gauge.LabelsQuantity = TConvert.ToInt(this.majorTicksQuantityDouble);
#endif

                    double interval = (gScale.EndValue - gScale.StartValue) / (quantity - 1);                                 
                    gScale.Interval = this.RoundDown(interval, 3);
                }

                if (this.minorTicksPerIntervalRun != null)
                {
                    gScale.MinorTicksPerInterval = TK.To<int>(await this.minorTicksPerIntervalRun.GetValueAsync());
                    if (gScale.MinorTicksPerInterval > 10)
                        gScale.MinorTicksPerInterval = 10;
                }

                if (this.rimThicknessRun != null)
                    gScale.RimStrokeThickness = TK.To<double>(await this.rimThicknessRun.GetValueAsync());

                if (this.gScale.Pointers != null && this.gScale.Pointers.Count > 0)
                {
                    if (this.linkedValueRun != null)
                        gScale.Pointers[0].Value = TK.To<double>(await this.linkedValueRun.GetValueAsync());

                    if (gScale.Pointers[0].Value < gScale.StartValue)
                        gScale.Pointers[0].Value = gScale.StartValue;
                    if (gScale.Pointers[0].Value > gScale.EndValue)
                        gScale.Pointers[0].Value = gScale.EndValue;

                    if (this.pointerThicknessRun != null)
                    {
                        gScale.Pointers[0].RangePointerStrokeThickness = TK.To<double>(await this.pointerThicknessRun.GetValueAsync());
                        gScale.Pointers[0].NeedlePointerStrokeThickness = gScale.Pointers[0].RangePointerStrokeThickness;
                    }

                    if (gScale.Pointers.Count > 1)
                    {
                        gScale.Pointers[1].Value = gScale.Pointers[0].Value;
                        if (this.pointerThicknessRun != null)
                        {
                            gScale.Pointers[1].RangePointerStrokeThickness = TK.To<double>(await this.pointerThicknessRun.GetValueAsync());
                            gScale.Pointers[1].NeedlePointerStrokeThickness = gScale.Pointers[1].RangePointerStrokeThickness;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                WK.ExceptionLog(e);
            }
        }

        public void GetStrings(object replaceControl)
        {
            if (HasStaticHeader)
                WK.GetStrings(replaceControl, this._headerLink, eFieldTypeTk.String);
        }

        public void ApplyStrings(object replaceControl)
        {
            if (HasStaticHeader)
                WK.ApplyStrings(replaceControl, ref this._headerLink, eFieldTypeTk.String);
        }

        public void GetLabels(object symbol)
        {
            WK.GetLabel(symbol, MinValueLink, eFieldTypeTk.NumberOrObject);
            WK.GetLabel(symbol, MaxValueLink, eFieldTypeTk.NumberOrObject);
            WK.GetLabel(symbol, LinkedValue, eFieldTypeTk.Expression);
            WK.GetLabel(symbol, HeaderLink, eFieldTypeTk.ObjectOrString);
            WK.GetLabel(symbol, RimThicknessLink, eFieldTypeTk.NumberOrObject);
            WK.GetLabel(symbol, PointerThicknessLink, eFieldTypeTk.NumberOrObject);
            WK.GetLabel(symbol, MajorTicksQuantityLink, eFieldTypeTk.NumberOrObject);
            WK.GetLabel(symbol, MinorTicksPerIntervalLink, eFieldTypeTk.NumberOrObject);
        }

        public void ResolveLabels(object symbol)
        {
            WK.ResolveLabel(symbol, ref _minValueLink, eFieldTypeTk.NumberOrObject);
            WK.ResolveLabel(symbol, ref _maxValueLink, eFieldTypeTk.NumberOrObject);
            WK.ResolveLabel(symbol, ref _linkedValue, eFieldTypeTk.Expression);
            WK.ResolveLabel(symbol, ref this._headerLink, eFieldTypeTk.ObjectOrString);
            WK.ResolveLabel(symbol, ref this._rimThicknessLink, eFieldTypeTk.NumberOrObject);
            WK.ResolveLabel(symbol, ref this._pointerThicknessLink, eFieldTypeTk.NumberOrObject);
            WK.ResolveLabel(symbol, ref this._majorTicksQuantityLink, eFieldTypeTk.NumberOrObject);
            WK.ResolveLabel(symbol, ref this._minorTicksPerIntervalLink, eFieldTypeTk.NumberOrObject);
        }

        public void GetTokens(object symbol)
        {
            WK.GetTokens(symbol, MinValueLink, eFieldTypeTk.NumberOrObject);
            WK.GetTokens(symbol, MaxValueLink, eFieldTypeTk.NumberOrObject);
            WK.GetTokens(symbol, LinkedValue, eFieldTypeTk.Expression);
            WK.GetTokens(symbol, HeaderLink, eFieldTypeTk.ObjectOrString);
            WK.GetTokens(symbol, RimThicknessLink, eFieldTypeTk.NumberOrObject);
            WK.GetTokens(symbol, PointerThicknessLink, eFieldTypeTk.NumberOrObject);
            WK.GetTokens(symbol, MajorTicksQuantityLink, eFieldTypeTk.NumberOrObject);
            WK.GetTokens(symbol, MinorTicksPerIntervalLink, eFieldTypeTk.NumberOrObject);
        }

        public void ApplyTokens(object symbol)
        {
            MinValueLink = WK.ApplyTokens(symbol, MinValueLink, eFieldTypeTk.NumberOrObject);
            MaxValueLink = WK.ApplyTokens(symbol, MaxValueLink, eFieldTypeTk.NumberOrObject);
            LinkedValue = WK.ApplyTokens(symbol, LinkedValue, eFieldTypeTk.Expression);
            HeaderLink = WK.ApplyTokens(symbol, HeaderLink, eFieldTypeTk.ObjectOrString);
            RimThicknessLink = WK.ApplyTokens(symbol, RimThicknessLink, eFieldTypeTk.NumberOrObject);
            PointerThicknessLink = WK.ApplyTokens(symbol, PointerThicknessLink, eFieldTypeTk.NumberOrObject);
            MajorTicksQuantityLink = WK.ApplyTokens(symbol, MajorTicksQuantityLink, eFieldTypeTk.NumberOrObject);
            MinorTicksPerIntervalLink = WK.ApplyTokens(symbol, MinorTicksPerIntervalLink, eFieldTypeTk.NumberOrObject);
        }

        public void OnFinishClipboardCopy()
        {
            MinValueLink = WK.OnFinishClipboardCopy(MinValueLink);
            MaxValueLink = WK.OnFinishClipboardCopy(MaxValueLink);
            LinkedValue = WK.OnFinishClipboardCopy(LinkedValue);
            HeaderLink = WK.OnFinishClipboardCopy(HeaderLink);
            RimThicknessLink = WK.OnFinishClipboardCopy(RimThicknessLink);
            PointerThicknessLink = WK.OnFinishClipboardCopy(PointerThicknessLink);
            MajorTicksQuantityLink = WK.OnFinishClipboardCopy(MajorTicksQuantityLink);
            MinorTicksPerIntervalLink = WK.OnFinishClipboardCopy(MinorTicksPerIntervalLink);
        }

        public void OnFinishClipboardPaste()
        {
            MinValueLink = WK.OnFinishClipboardPaste(MinValueLink);
            MaxValueLink = WK.OnFinishClipboardPaste(MaxValueLink);
            LinkedValue = WK.OnFinishClipboardPaste(LinkedValue);
            HeaderLink = WK.OnFinishClipboardPaste(HeaderLink);
            RimThicknessLink = WK.OnFinishClipboardPaste(RimThicknessLink);
            PointerThicknessLink = WK.OnFinishClipboardPaste(PointerThicknessLink);
            MajorTicksQuantityLink = WK.OnFinishClipboardPaste(MajorTicksQuantityLink);
            MinorTicksPerIntervalLink = WK.OnFinishClipboardPaste(MinorTicksPerIntervalLink);
        }

        public void OnStartClipboardCopy()
        {
            MinValueLink = WK.OnStartClipboardCopy(MinValueLink);
            MaxValueLink = WK.OnStartClipboardCopy(MaxValueLink);
            LinkedValue = WK.OnStartClipboardCopy(LinkedValue);
            HeaderLink = WK.OnStartClipboardCopy(HeaderLink);
            RimThicknessLink = WK.OnStartClipboardCopy(RimThicknessLink);
            PointerThicknessLink = WK.OnStartClipboardCopy(PointerThicknessLink);
            MajorTicksQuantityLink = WK.OnStartClipboardCopy(MajorTicksQuantityLink);
            MinorTicksPerIntervalLink = WK.OnStartClipboardCopy(MinorTicksPerIntervalLink);
        }

        public void OnSave(int displayId, object el)
        {
            OnStartClipboardCopy();
            OnFinishClipboardCopy();

            WK.SaveHelper.SaveStart(displayId, el);

            WK.SaveHelper.SaveLink(LinkedValue, "LinkedValue");
            WK.SaveHelper.SaveLink(MinValueLink, "MinValue");
            WK.SaveHelper.SaveLink(MaxValueLink, "MaxValue");
            WK.SaveHelper.SaveLink(HeaderLink, "HeaderLink");
            WK.SaveHelper.SaveLink(RimThicknessLink, "RimThickness");
            WK.SaveHelper.SaveLink(PointerThicknessLink, "PointerThickness");
            WK.SaveHelper.SaveLink(MajorTicksQuantityLink, "MajorTicksQuantity");
            WK.SaveHelper.SaveLink(MinorTicksPerIntervalLink, "MinorTicksPerInterval");

            if (HasStaticHeader && _localize)
                WK.SaveHelper.AddLocalizableString(this._headerLink);
        }

        #endregion

        #region Aux Methods

        private void UpdateGridContent()
        {
            this.mainGrid.Children.Clear();
            this.mainGrid.RowDefinitions.Clear();

            GridUnitType gridUnitTypeRow0 = GridUnitType.Auto;
            GridUnitType gridUnitTypeRow1 = GridUnitType.Star;

            int rowTitle = 1;
            int rowGauge = 0;
            HorizontalAlignment horizontalAlignmentTitle = HorizontalAlignment.Center;

            switch (this.HeaderAlignment)
            {
                case TK.eTkHeaderAlignment.Top:
                case TK.eTkHeaderAlignment.TopLeft:
                case TK.eTkHeaderAlignment.TopRight:
                    rowTitle = 0;
                    rowGauge = 1;
                    gridUnitTypeRow0 = GridUnitType.Auto;
                    gridUnitTypeRow1 = GridUnitType.Star;
                    horizontalAlignmentTitle = HorizontalAlignment.Center;
                    if (this.HeaderAlignment == TK.eTkHeaderAlignment.TopLeft)
                        horizontalAlignmentTitle = HorizontalAlignment.Left;
                    if (this.HeaderAlignment == TK.eTkHeaderAlignment.TopRight)
                        horizontalAlignmentTitle = HorizontalAlignment.Right;
                    break;

                case TK.eTkHeaderAlignment.Bottom:
                case TK.eTkHeaderAlignment.BottomLeft:
                case TK.eTkHeaderAlignment.BottomRight:
                    rowTitle = 1;
                    rowGauge = 0;
                    horizontalAlignmentTitle = HorizontalAlignment.Center;
                    gridUnitTypeRow0 = GridUnitType.Star;
                    gridUnitTypeRow1 = GridUnitType.Auto;
                    if (this.HeaderAlignment == TK.eTkHeaderAlignment.BottomLeft)
                        horizontalAlignmentTitle = HorizontalAlignment.Left;
                    if (this.HeaderAlignment == TK.eTkHeaderAlignment.BottomRight)
                        horizontalAlignmentTitle = HorizontalAlignment.Right;
                    break;

                default:
                    break;
            }


            this.mainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, gridUnitTypeRow0) });
            this.mainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, gridUnitTypeRow1) });

            this.mainGrid.Children.Add(this.gauge);
            Grid.SetRow(this.gauge, rowGauge);

            this.mainGrid.Children.Add(this.gHeader);
            Grid.SetRow(this.gHeader, rowTitle);

            this.gHeader.HorizontalAlignment = horizontalAlignmentTitle;

            this.Content = this.mainGrid;

        }

        private bool IsStaticValue(string value)
        {
            if (string.IsNullOrEmpty(value))
                return false;

            if (value.Contains("@") || (value.Contains("{") && value.Contains("}")))
                return false;

            return true;
        }

        #endregion
    }
}
SampleGauge/TSampleGaugeConfig.xaml
<UserControl x:Class="T.Portable.Controls.TSampleGaugeConfig"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:ttw="clr-namespace:T.Toolkit.Wpf;assembly=T.Toolkit.Wpf"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             xmlns:ttc="clr-namespace:T.Portable.Controls"
             xmlns:gauge="clr-namespace:Syncfusion.UI.Xaml.Gauges;assembly=Syncfusion.SfGauge.Wpf"
             d:DataContext="{d:DesignInstance Type=ttc:TSampleGauge}" 
             mc:Ignorable="d"
             d:DesignHeight="340"
             d:DesignWidth="440.982"
             Loaded="UserControl_Loaded">
    <UserControl.Resources>

        <ObjectDataProvider x:Key="PointerType" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="gauge:PointerType" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
        
    </UserControl.Resources>
    <Grid >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="140" />
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="28" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Label Content="Header:"
               Grid.Row="0"
               VerticalAlignment="Center"
               HorizontalAlignment="Right" />
        <ttw:FieldEditor EditInfo="{Binding Path=ObjectOrStringField}" Text="{Binding Path=HeaderLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch" />

        <Label Grid.Row="1" Grid.Column="0" Content="Header FontSize:" VerticalAlignment="Center" HorizontalAlignment="Right" />
        
        <Grid Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto" MinWidth="50"/>
                <ColumnDefinition Width="auto" />
                <ColumnDefinition Width="auto" MinWidth="105"/>
                <ColumnDefinition Width="auto"/>
            </Grid.ColumnDefinitions>


            <ComboBox ItemsSource="{Binding Path=HeaderFontSizes}" SelectedItem="{Binding Path=HeaderFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Width="50" SelectedIndex="6" IsEditable="True" />

            <Label Content="Alignment:" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="4,0,0,0" />
            <ComboBox Grid.Column="2" Margin="4,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=HeaderAlignments}" SelectedItem="{Binding Path=HeaderAlignment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

            <CheckBox Content="Localize" IsChecked="{Binding Path=Localize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" Grid.Column="3" Margin="4,0,0,0" />
        </Grid>





        <Label  Content="Linked Value:"
               Grid.Row="2"
               VerticalAlignment="Center"
               HorizontalAlignment="Right" />
        <ttw:FieldEditor EditInfo="{Binding Path=ExpressionField}"
                         Text="{Binding Path=LinkedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                 
                         Grid.Column="1" Grid.ColumnSpan="2"
                         Grid.Row="2"
                         VerticalAlignment="Center"
                         HorizontalAlignment="Stretch"/>

        <Label  Content="Minimum Value:"
               Grid.Row="3"
               VerticalAlignment="Center"
               HorizontalAlignment="Right"
               />
        <ttw:FieldEditor Text="{Binding Path=MinValueLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         EditInfo="{Binding Path=NumberOrObjectField}"
                         Grid.Column="1" Grid.ColumnSpan="2" 
                         Grid.Row="3"
                         VerticalAlignment="Center"
                         HorizontalAlignment="Stretch"/>
        
        <Label  Content="Maximum Value:"
               Grid.Row="4"
               VerticalAlignment="Center"
               HorizontalAlignment="Right"
                />
        <ttw:FieldEditor Text="{Binding Path=MaxValueLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         EditInfo="{Binding Path=NumberOrObjectField}"
                         Grid.Column="1" Grid.ColumnSpan="2"
                         Grid.Row="4"
                         VerticalAlignment="Center"
                         HorizontalAlignment="Stretch" />

        <Label 
               Grid.Row="5"
               Content="Label FontSize:"
               VerticalAlignment="Center"
               HorizontalAlignment="Right"
               />
        <ComboBox  ItemsSource="{Binding Path=HeaderFontSizes}"
            SelectedItem="{Binding Path=ScaleFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  Grid.Column="1"
                  Grid.Row="5"
                  VerticalAlignment="Center"
                  HorizontalAlignment="Left"
                  Width="50" />


        <Label Content="Rim Thickness:"
               Grid.Row="6"
               VerticalAlignment="Center"
               HorizontalAlignment="Right" />
        <ttw:FieldEditor Text="{Binding Path=RimThicknessLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         EditInfo="{Binding Path=NumberOrObjectField}"
                         Grid.Column="1"
                         Grid.Row="6"
                         VerticalAlignment="Center"
                         HorizontalAlignment="Stretch" />

        <StackPanel Orientation="Horizontal" Grid.Row="6" Grid.Column="2" Margin="10,0,0,0" Grid.ColumnSpan="2">
            
            <ttw:BrushEditorButton SelectedBrush="{Binding Path=RimStroke, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                 Height="20"
                                 Width="40"
                                 AllowThemeColors="False"
                                 VerticalAlignment="Center" />
            
            <Label  Content="Rim Stroke" Margin="4,0,0,0"
               VerticalAlignment="Center" />

        </StackPanel>
        
        <Label  Content="Pointer Thickness:"
               Grid.Row="7"
               VerticalAlignment="Center"
               HorizontalAlignment="Right" />
        <ttw:FieldEditor  Text="{Binding Path=PointerThicknessLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          EditInfo="{Binding Path=NumberOrObjectField}"
                          Grid.Column="1"
                          Grid.Row="7"
                          VerticalAlignment="Center"
                          HorizontalAlignment="Stretch" />
        <StackPanel Orientation="Horizontal"
                    Grid.Row="7" Grid.Column="2" Margin="10,0,0,0" Grid.ColumnSpan="2">
            
            <ttw:BrushEditorButton SelectedBrush="{Binding Path=PointerStroke, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                 Height="20"
                                 Width="40"
                                 AllowThemeColors="False"
                                 VerticalAlignment="Center" />
            
            <Label  Content="Pointer Stroke" Margin="4,0,0,0"
               VerticalAlignment="Center" />

        </StackPanel>

        <Label  Content="Pointer Type:"
               Grid.Row="8"
               VerticalAlignment="Center"
               HorizontalAlignment="Right" />
        <ComboBox  ItemsSource="{Binding Source={StaticResource PointerType}}"
                   SelectedItem="{Binding Path=PointerType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                   VerticalAlignment="Center"
                   Grid.Column="1"
                   Grid.Row="8"
                   >
        </ComboBox>

        <CheckBox Content="Show NeedlePointer" IsChecked="{Binding ShowNeedlePointer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  IsEnabled="{Binding Path=CanSecondPointer}"
                   VerticalAlignment="Center"
                   Margin="10,0,0,0"
                   Grid.Column="2"
                   Grid.Row="8"
                  />


        <CheckBox IsChecked="{Binding Path=ShowTicks, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  Grid.Column="2"
                  Grid.ColumnSpan="2"
                  Content="Show Ticks"
                  Grid.Row="5"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Center"
                   />
        <CheckBox    IsChecked="{Binding Path=ShowLabels, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Grid.Column="1"
                  Grid.ColumnSpan="2"
                  Content="Show Labels"
                  Grid.Row="5"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Center"
                  Margin="55,0,0,0"
                  />
        
        <Label  Content="MajorTicks Quantity:"
               Grid.Row="9"
               VerticalAlignment="Center"
               HorizontalAlignment="Right"/>
        <ttw:FieldEditor Text="{Binding Path=MajorTicksQuantityLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"     
                         EditInfo="{Binding Path=NumberOrObjectField}"
                         Grid.Column="1"
                         Grid.Row="9"
                         VerticalAlignment="Center"
                         HorizontalAlignment="Stretch" />

        <Label  Content="MinorTicks per Interval:"
               Grid.Row="10"
               VerticalAlignment="Center"
               HorizontalAlignment="Right"/>
        <ttw:FieldEditor Text="{Binding Path=MinorTicksPerIntervalLink, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         EditInfo="{Binding Path=NumberOrObjectField}"
                         Grid.Column="1"
                         Grid.Row="10"
                         VerticalAlignment="Center"
                         HorizontalAlignment="Stretch" />
    </Grid>
</UserControl>
SampleGauge/TSampleGaugeConfig.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;

using T.Toolkit;

namespace T.Portable.Controls
{

    public partial class TSampleGaugeConfig : UserControl, IControlConfig, IOnOK
    {
        #region Fields
        private TSampleGauge controlRef = null;
        #endregion Fields

        public TSampleGaugeConfig()
        {
            InitializeComponent();
        }

        #region Public Methods

        public void LoadControl(object _control)
        {
            this.controlRef =  _control as TSampleGauge;
            this.DataContext = this.controlRef;
        }

        public bool OnOK()
        {
            return true;
        }

        #endregion Public Methods

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {

        }
    }
}
SampleGauge.HTML5/SfCircularGauge.cs
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using T.Library;
using T.Toolkit.Wpf;

namespace T.Portable.Components.Gauges
{

    #region Enums

    public enum CircularGaugeTypes
    {
        Default,
        East,
        West,
        North,
        South,
        NorthEast,
        NorthWest,
        SouthEast,
        SouthWest
    }

    public enum HeaderAlignment
    {
        Left,
        Right,
        Center,
        Top,
        Bottom,
        TopLeft,
        TopRight,
        BottomLeft,
        BottomRight,
        Custom
    }

    public enum TickShape
    {
        Ellipse,
        Rectangle,
        Triangle
    }

    public enum PointerType
    {
        NeedlePointer,
        RangePointer,
        SymbolPointer
    }

    public enum NumericScaleType
    {
        Auto,
        Thousands,
        Millions,
        Billions,
        Trillions,
        Quadrillions,
        Quintillions
    }

    public enum LabelPosition
    {
        Inside,
        Outside,
        Custom
    }

    public enum TickPosition
    {
        Inside,
        Outside,
        Cross,
        Custom
    }

    public enum RangePointerPosition
    {
        Inside,
        Outside,
        Cross,
        Custom
    }

    public enum RangePosition
    {
        Inside,
        SetAsGaugeRim,
        Outside,
        Custom
    }

    public enum Symbol
    {
        Rectangle,
        RoundedRectangle,
        Ellipse,
        Triangle,
        InvertedTriangle,
        Arrow,
        InvertedArrow,
        Hexagon,
        Pentagon,
        Cross,
        Diamond,
        Custom
    }

    public enum NeedlePointerType
    {
        Rectangle,
        Triangle,
        Arrow,
        Tapered
    }

    public enum RangeCap
    {
        None,
        Start,
        End,
        Both
    }

    public enum ViewAlignment
    {
        Start,
        Center,
        End
    }

    #endregion

    #region SfCircularGauge class

    public class SfCircularGauge : Canvas, IDisposable
    {
        #region Fields
        internal object circularGauge = null;
        private object gaugeHeader = null;
        public bool IsCompass = false;
        #endregion

        #region Properties
        public int LabelsQuantity { get; set; } = 10;
        public int LabelsCreated { get; set; } = 0;

        private HeaderAlignment headerAlignment = HeaderAlignment.Top;
        public HeaderAlignment HeaderAlignment 
        { 
            get => headerAlignment;
            set
            {
                headerAlignment = value;
                if (this.circularGauge == null)
                    return;
                OpenSilver.Interop.ExecuteJavaScript("$0.titleStyle.position = $1", this.circularGauge, headerAlignmentAsString);
            }
        }

        private string headerAlignmentAsString
        {
            get
            {
                if (HeaderAlignment == HeaderAlignment.Bottom)
                    return "Bottom";

                if (HeaderAlignment == HeaderAlignment.Left)
                    return "Left";

                if (HeaderAlignment == HeaderAlignment.Right)
                    return "Right";

                return "Top";
            }
        }

        private TextBlock GaugeHeaderTextBlock
        {
            get => GaugeHeader as TextBlock;
        }

        public double GaugeHeight 
        {
            set
            {
                if (this.circularGauge == null)
                    return;

                string height = "100%";
                if (!double.IsNaN(value))
                    height = TConvert.ToString(value > 100 ? value : 100);

                OpenSilver.Interop.ExecuteJavaScript("$0.height = $1", this.circularGauge, height);
            }
        }

        public double GaugeWidth
        {
            set
            {
                if (this.circularGauge == null)
                    return;

                string width = "100%";
                string radiusWidth = "100%";
                if (!double.IsNaN(value))
                {
                    radiusWidth = TConvert.ToString(value / 2);
                    width = TConvert.ToString(value > 100 ? value : 100);
                }


                OpenSilver.Interop.ExecuteJavaScript("$0.width = $1", this.circularGauge, width);
            }
        }


        /// <summary>
        /// This object is a TxtBlock
        /// </summary>
        public object GaugeHeader 
        {
            get
            {
                TextBlock textBlock = new TextBlock
                {
                    Text = "Header",
                    FontFamily = new FontFamily("Verdana"),
                    FontSize = 12,
                    FontWeight = FontWeights.Normal,
                    Foreground = WK.GetBrush(WK.eThemeColor.TextForeground) as Brush,
                };

                textBlock.Text = string.Empty;
                
                if(this.gaugeHeader == null)
                    this.gaugeHeader = textBlock;
                

                return this.gaugeHeader;
            }
            set
            {
                if (this.circularGauge == null)
                    return;

                if (value == null)
                    return;

                if (!(value is TextBlock))
                    return;



                this.gaugeHeader = value;
            }
        }

        public CircularGaugeTypes GaugeType { get; set; }
        public Point GaugeHeaderPosition { get; set; }
        public ObservableCollection<CircularScale> SubScales { get; set; }
    
        private CircularScale _mainScale;
        /// <summary>
        /// Represents the <b>Axes</b> object of the WebAssembly Gauge
        /// </summary>
        public CircularScale MainScale
        {
            get => _mainScale;
            set
            {
                _mainScale = value;
                //if (value == null)
                //    OpenSilver.Interop.ExecuteJavaScript("$0.axes.shift()", this.circularGauge);
                //else
                //    OpenSilver.Interop.ExecuteJavaScript("$0.axes.unshift($1)", this.circularGauge, value.circularScale);

            }
        }

        public Style CircularScaleStyle { get; set; }
        public double SpacingMargin { get; set; }
        public CircularGaugeAnnotationCollection Annotations { get; set; }
        public ObservableCollection<CircularScale> Scales { get; set; }
      
        #endregion

        #region Construtor, Load and Dispose

        public SfCircularGauge()
        {
            this.Scales = new ObservableCollection<CircularScale>();
            this.SubScales = new ObservableCollection<CircularScale>();
            this.Annotations = new CircularGaugeAnnotationCollection();

            this.HorizontalAlignment = HorizontalAlignment.Stretch;
            this.VerticalAlignment = VerticalAlignment.Stretch;

            this.Loaded += this.OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            string initialTitle = this.GaugeHeaderTextBlock.Text ?? "";
            double initialFontSize = this.GaugeHeaderTextBlock.FontSize;
            if (initialFontSize <= 0)
                initialFontSize = 12;

            object pointers = OpenSilver.Interop.ExecuteJavaScript("[]");
            if(this.Scales.Count > 0)
            {
                int numberOfPointers = this.Scales[0].Pointers.Count;
                for (int i = 0; i < numberOfPointers; i++)
                    OpenSilver.Interop.ExecuteJavaScript("$0.push({})", pointers);
            }

            /// There will always be a defualt range to simbolize the Rim Stroke and Thickness
            object ranges = OpenSilver.Interop.ExecuteJavaScript("[{}]");
            if (this.Scales.Count > 0)
            {
                int numberOfRanges = this.Scales[0].Ranges.Count;
                for (int i = 0; i < numberOfRanges; i++)
                    OpenSilver.Interop.ExecuteJavaScript("$0.push({})", ranges);
            }

            object annotations = OpenSilver.Interop.ExecuteJavaScript("[]");
            if (this.Annotations.Count > 0)
            {
                int numberOfAnnotations = this.Annotations.Count;
                for (int i = 0; i < numberOfAnnotations; i++)
                    OpenSilver.Interop.ExecuteJavaScript("$0.push({})", annotations);
            }


            string width = "100%";
            string height = "100%";
            string radiusWidth = "100%";
            if (!double.IsNaN(this.Width))
            {
                radiusWidth = TConvert.ToString(Math.Min(this.Width, this.Height) / 2);
                width = TConvert.ToString(this.Width > 100 ? this.Width : 100);
            }

            if (!double.IsNaN(this.Height))            
                height = TConvert.ToString(this.Height > 100 ? this.Height : 100);            

            object div = OpenSilver.Interop.GetDiv(this);
            string id = TConvert.ToString(OpenSilver.Interop.ExecuteJavaScript("$0.id", div));
            object parentDiv = OpenSilver.Interop.ExecuteJavaScript("$0.parentNode", div);
      
            this.circularGauge = OpenSilver.Interop.ExecuteJavaScript(@"new ej.circulargauge.CircularGauge({
               background : 'transparent',                            
               enablePersistance: true,
               annotations: $0,
               axes: [{
                     pointers: $1,
                     ranges: $2,
                     radius: $3,
            }],
            })", annotations, pointers, ranges, radiusWidth);

            OpenSilver.Interop.ExecuteJavaScript("$0.width = $1", this.circularGauge, width);
            OpenSilver.Interop.ExecuteJavaScript("$0.height = $1", this.circularGauge, height);

            this.LabelsCreated = 0;

            if (this.IsCompass)
            {
                OpenSilver.Interop.ExecuteJavaScript(@"$0.axisLabelRender = function (args) {
                        var value = ['E', 'SE', 'S', 'SW', 'W', 'NW', 'N', 'NE', 'E'];
                        var pos = args.value;
                        if(pos < 0) { pos = 0; }
                        if(pos > 8)  { pos = 8; }
                    
                        args.text = value[args.value];
                    }", this.circularGauge);
            }
            else
                OpenSilver.Interop.ExecuteJavaScript("$0.axisLabelRender = $1", this.circularGauge, OpenSilver.Interop.CreateJavascriptCallback((Action<object>)this.OnAxisLabelRender));

            OpenSilver.Interop.ExecuteJavaScript("$0.appendTo($1)", this.circularGauge, "#" + id);

#if DEBUG
            /// TEMP -> Variable for easy debug in Browser console
            OpenSilver.Interop.ExecuteJavaScript("window[$0] = $1", "sfCircularGauge_" + id, this.circularGauge);
#endif


            OpenSilver.Interop.ExecuteJavaScript("$0.style.overflow = 'hidden'", div);
            if (this.Scales.Count > 0)
            {
                foreach (var scale in this.Scales)                
                    scale.CircularGaugeWebAssemblyObject = this.circularGauge;
            }
                
            if (this.Annotations.Count > 0)
            {
                this.Annotations.CircularGaugeWebAssemblyObject = this.circularGauge;
            }
        }

        public void Dispose()
        {

        }

        #endregion

        #region Static Methods

        public static string BrushToRgbaString(Brush brush)
        {
            if (brush is SolidColorBrush solidColorBrush)
            {
                Color color = solidColorBrush.Color;
                return ColorToRgbaString(color);
            }

            return ColorToRgbaString(Colors.Transparent);
        }

        public static string ColorToRgbaString(Color color)
        {
            double alpha = color.A / 255.0;
            return $"rgba({color.R}, {color.G}, {color.B}, {alpha.ToString(CultureInfo.InvariantCulture)})";
        }

        #endregion
        public void Refresh()
        {
            if (this.circularGauge == null)
                return;
            OpenSilver.Interop.ExecuteJavaScript("$0.refresh()", this.circularGauge);
        }

        internal void UpdateAxesRadius()
        {
            if (double.IsNaN(this.Height) || double.IsNaN(this.Width))
                return;

            double _height = this.Height > 100 ? this.Height : 100;
            double _width = this.Width > 100 ? this.Width : 100;
            //double radius = Math.Min(_height, _width) / 2;
            //radius = (radius + (this.Scales[0].RimStrokeThickness / 2));

            double centerX = _width / 2;
            double centerY = _height / 1.5;
            string radiusPercentage = "120%";


            if (this.MainScale.SweepAngle >= 270)
            {
                double factor = this.MainScale.SweepAngle == 360 ? 2 : 1.85;
                centerY = _height / factor;
                radiusPercentage = "100%";
            }

            OpenSilver.Interop.ExecuteJavaScript("$0.centerY  = $1", this.circularGauge, TConvert.ToString(centerY));
            OpenSilver.Interop.ExecuteJavaScript("$0.centerX  = $1", this.circularGauge, TConvert.ToString(centerX));
            OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].radius = $1", this.circularGauge, radiusPercentage);
        }

        private void OnAxisLabelRender(object args)
        {
            try
            {
                this.LabelsCreated += 1;
                string originalText = TConvert.ToString(OpenSilver.Interop.ExecuteJavaScript("$0.text", args));
                double roundedValue = Math.Round(TConvert.ToDouble(originalText), 2);
                OpenSilver.Interop.ExecuteJavaScript("$0.text = $1", args, roundedValue);

                if (this.LabelsCreated < this.LabelsQuantity)
                    return;

                double endValue = this.Scales[0].EndValue;
                OpenSilver.Interop.ExecuteJavaScript("$0.text = $1", args, endValue);
                this.LabelsCreated = 0;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error in OnAxisLabelRender : {ex.Message}");
                TException.Log(ex);
            }
        }

    }


    #endregion

    #region Auxiliary Classes

    public class CircularScaleLabel
    {
        public object Content { get; set; }
        public Brush Foreground { get; set; }
        public double Angle { get; set; }
    }
    public class CircularScaleLabelCollection : ObservableCollection<CircularScaleLabel>
    {
    }

    public class CircularScaleTick
    {
        public TickShape TickShape { get; set; }
    }
    public class CircularScaleTickCollection : ObservableCollection<CircularScaleTick>
    {
    }

    public class GaugeGradientStop
    {
        public Color Color { get; set; }
        public double Value { get; set; }
        internal double ActualValue { get; set; }
    }
    public class GaugeGradientStopCollection : ObservableCollection<GaugeGradientStop>
    {
    }

    public class CircularRange
    {
        public object RangeIndex { get; set; } = 0;
        public object Tag { get; set; }
        internal double InternalRangeOffset = 1.0;
        public PathSegmentCollection RangeSeg { get; set; }
        internal double RangeSize { get; set; }
        internal Size ParentSize { get; set; }
        internal double ParentStartValue { get; set; }
        internal double ParentEndValue { get; set; }
        internal double ParentStartAngle { get; set; }
        internal double ClockWiseStartAngle { get; set; }
        internal double ParentSweepAngle { get; set; }
        internal CircularScale ParentScale { get; set; }

        private double startValue = 0;
        public double StartValue
        {
            get => startValue;
            set
            {
                startValue = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;
    
                this.LoadCircularRangesFromWebAssemblyControl();
                OpenSilver.Interop.ExecuteJavaScript("$0.start = $1", circularRange, startValue);
            }
        }

        private double endValue = 0;
        public double EndValue
        {
            get => endValue;
            set
            {
                endValue = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;
 
                this.LoadCircularRangesFromWebAssemblyControl();
                OpenSilver.Interop.ExecuteJavaScript("$0.end = $1", circularRange, endValue);
            }
        }
        internal double InternalEndValue { get; set; }
        internal double StartAngle { get; set; }
        public double EndAngle { get; set; }
        public Point StartPoint { get; set; }
        public Point EndPoint { get; set; }
        public Size Size { get; set; }
        internal bool IsLargeArc { get; set; }
        internal SweepDirection SweepDirection { get; set; }
    

        private Visibility visibility = Visibility.Collapsed;
        public Visibility Visibility 
        {
            get { return this.visibility; } 
            set
            {
                this.visibility = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                this.LoadCircularRangesFromWebAssemblyControl();
                if(this.visibility == Visibility.Visible)
                    OpenSilver.Interop.ExecuteJavaScript("$0.opacity = 1.0", this.circularRange);
                if (this.visibility == Visibility.Collapsed)
                    OpenSilver.Interop.ExecuteJavaScript("$0.opacity = 0.0", this.circularRange);
            }
        }



        private Brush stroke;
        public Brush Stroke
        {
            get { return stroke; }
            set
            {
                this.stroke = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                this.LoadCircularRangesFromWebAssemblyControl();
                OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularRange, SfCircularGauge.BrushToRgbaString(this.stroke));
            }
        }

        private double strokeThickness = 2;
        public double StrokeThickness
        {
            get { return strokeThickness; }
            set
            {
                this.strokeThickness = value;
                this.StartWidth = value;
                this.EndWidth = value;
            }
        }
        internal int LogBase { get; set; }
        public bool IsLogarithmic { get; set; }
        public double StartWidth 
        { 
            get => TConvert.ToDouble(OpenSilver.Interop.ExecuteJavaScript("$0.startWidth", circularRange)); 
            set => OpenSilver.Interop.ExecuteJavaScript("$0.startWidth = $1", circularRange, value); 
        }
        public double EndWidth
        {
            get => TConvert.ToDouble(OpenSilver.Interop.ExecuteJavaScript("$0.endWidth", circularRange));
            set => OpenSilver.Interop.ExecuteJavaScript("$0.endWidth = $1", circularRange, value);
        }
        public GaugeGradientStopCollection GradientStops { get; set; }
        public double InnerStartOffset { get; set; }
        public double InnerEndOffset { get; set; }
        public double OuterStartOffset { get; set; }
        public double OuterEndOffset { get; set; }
        public double Offset { get; set; }

        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
                if (circularGaugeWebAssemblyObject == null)
                    return;
                this.ApplyInitialSettingsToWebAssemblyObject();
            }
        }

        private object circularRange;
        public CircularRange()
        {
            circularRange = OpenSilver.Interop.ExecuteJavaScript("new Object()");
        }

        private void ApplyInitialSettingsToWebAssemblyObject()
        {
            this.LoadCircularRangesFromWebAssemblyControl();

            OpenSilver.Interop.ExecuteJavaScript("$0.start = $1", this.circularRange, this.StartValue);
            OpenSilver.Interop.ExecuteJavaScript("$0.end = $1", this.circularRange, this.EndValue);
            OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularRange, SfCircularGauge.BrushToRgbaString(this.Stroke));
            OpenSilver.Interop.ExecuteJavaScript("$0.startWidth = $1", this.circularRange, this.StartWidth);
            OpenSilver.Interop.ExecuteJavaScript("$0.endWidth = $1", this.circularRange, this.EndWidth);

            if (this.visibility == Visibility.Visible)
                OpenSilver.Interop.ExecuteJavaScript("$0.opacity = 1.0", this.circularRange);
            if (this.visibility == Visibility.Collapsed)
                OpenSilver.Interop.ExecuteJavaScript("$0.opacity = 0.0", this.circularRange);
         
            this.SaveCircularRangesToWebAssemblyControl();
        }

        /// <summary>
        /// Method used to Load Range object that is currently being used in WebAssembly Runtime
        /// </summary>
        private void LoadCircularRangesFromWebAssemblyControl()
        {
            this.circularRange = OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].ranges[$1]", this.CircularGaugeWebAssemblyObject, this.RangeIndex);
        }

        /// <summary>
        /// Method used to replace current Range object with new one
        /// Used when trying to go back to default properties.
        /// </summary>
        private void SaveCircularRangesToWebAssemblyControl()
        {
            OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].ranges[$1] = $2", this.CircularGaugeWebAssemblyObject, this.RangeIndex, this.circularRange);
        }

    }
    public class CircularRangeCollection : ObservableCollection<CircularRange>
    {
        internal object circularRange = null;
        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
                if (circularGaugeWebAssemblyObject == null)
                    return;

                for (int i = 0; i < this.Count; i++)
                    this.Items[i].CircularGaugeWebAssemblyObject = circularGaugeWebAssemblyObject;
            }
        }

        public CircularRangeCollection(object circularScale)
        {
            this.circularRange = circularScale;
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);

            if (e.Action != NotifyCollectionChangedAction.Add)
                return;

            for (int i = 0; i < this.Count; i++)
                this.Items[i].RangeIndex = i + 1; /// The index starts at 1 because we have created a default range to act as the Rim Stroke and Thickness
        }
    }

    public class CircularPointer
    {
        public int PointerIndex { get; set; } = 0;
        public double Width { get; set; }
        public double Height { get; set; }

        private Brush rangePointerStroke = new SolidColorBrush(Color.FromArgb(0xff, 0x00, 0x00, 0x90));
        public Brush RangePointerStroke
        {
            get { return needlePointerStroke; }
            set
            {
                if (this.PointerType != PointerType.RangePointer)
                    return;

                needlePointerStroke = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(rangePointerStroke));
                OpenSilver.Interop.ExecuteJavaScript("$0.cap.color = 'transparent'", this.circularPointer);
            }
        }

        private double rangePointerStrokeThickness = 10;
        public double RangePointerStrokeThickness
        {
            get { return rangePointerStrokeThickness; }
            set
            {
                if (this.PointerType == PointerType.NeedlePointer)
                    return;

                rangePointerStrokeThickness = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;
                
                OpenSilver.Interop.ExecuteJavaScript("$0.pointerWidth = $1", this.circularPointer, rangePointerStrokeThickness);
            }
        }
        public Symbol Symbol { get; set; }

        private Brush symbolPointerStroke = new SolidColorBrush(Color.FromArgb(0xff, 0x00, 0x00, 0x90));
        public Brush SymbolPointerStroke
        {
            get { return needlePointerStroke; }
            set
            {
                if (this.PointerType != PointerType.SymbolPointer)
                    return;

                symbolPointerStroke = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(symbolPointerStroke));
                OpenSilver.Interop.ExecuteJavaScript("$0.cap.color = 'transparent'", this.circularPointer);
            }
        }

        public double SymbolPointerHeight { get; set; }
        public double SymbolPointerWidth { get; set; }
        public DataTemplate SymbolPointerTemplate { get; set; }

        private Brush needlePointerStroke = new SolidColorBrush(Color.FromArgb(0xff, 0x00, 0x00, 0x90));
        public Brush NeedlePointerStroke
        {
            get { return needlePointerStroke; }
            set
            {
                if (this.PointerType != PointerType.NeedlePointer)
                    return;

                needlePointerStroke = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(needlePointerStroke));
                OpenSilver.Interop.ExecuteJavaScript("$0.cap.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(needlePointerStroke));
            }
        }

        private double needlePointerStrokeThickness = 10;
        public double NeedlePointerStrokeThickness
        {
            get { return needlePointerStrokeThickness; }
            set
            {
                if (this.PointerType == PointerType.RangePointer)
                    return;

                needlePointerStrokeThickness = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.pointerWidth = $1", this.circularPointer, needlePointerStrokeThickness);
            }
        }
        public double NeedleLengthFactor { get; set; }
        public NeedlePointerType NeedlePointerType { get; set; }
        public Brush PointerCapStroke { get; set; }
        public double PointerCapDiameter { get; set; }
        public double KnobRadiusFactor { get; set; }
        public Brush KnobStroke { get; set; }
        public double KnobStrokeThickness { get; set; }
        public Brush KnobFill { get; set; }
        public Visibility RangePointerVisibility { get; set; }
        public Visibility SymbolPointerVisibility { get; set; }
        public Visibility NeedlePointerVisibility { get; set; }

        private double objValue = 0;
        public double Value
        {
            get => objValue;
            set 
            {
                objValue = value;
                if (this.CircularGaugeWebAssemblyObject == null || this.circularPointer == null)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.value = $1", this.circularPointer, objValue);
            }
        }
        public double RangeStart { get; set; }



        private PointerType pointerType = PointerType.NeedlePointer;
        /// <summary>
        /// In WPF this property is used to define the type of Pointer used <br/>
        /// In WebAssembly we must use property <b>PointerTypeString</b>
        /// </summary>
        public PointerType PointerType
        {
            get => pointerType;
            set
            {
                pointerType = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                this.LoadCircularPointerFromWebAssemblyControl();

                switch (pointerType)
                {
                    default:
                    case PointerType.NeedlePointer:
                        OpenSilver.Interop.ExecuteJavaScript("$0.type = 'Needle'", this.circularPointer);
                        return;
                    case PointerType.RangePointer:
                        OpenSilver.Interop.ExecuteJavaScript("$0.type = 'RangeBar'", this.circularPointer);
                        return;
                    case PointerType.SymbolPointer:
                        OpenSilver.Interop.ExecuteJavaScript("$0.type = 'Marker'", this.circularPointer);
                        return;
                }
            }
        }

        private string PointerTypeString
        {

            get
            {
                switch (pointerType)
                {
                    default:
                    case PointerType.NeedlePointer:                      
                        return "Needle";
                    case PointerType.RangePointer:
                        return "RangeBar";
                    case PointerType.SymbolPointer:
                        return "Marker";
                }
            }
        }

        public bool EnableAnimation { get; set; }
        public double Offset { get; set; }
        public bool EnableDragging { get; set; }
        public double RangeStartOffset { get; set; }
        public double RangeEndOffset { get; set; }
        public double TailLengthFactor { get; set; }
        public Brush TailFill { get; set; }
        public Brush TailStroke { get; set; }
        public double TailStrokeThickness { get; set; }
        public RangeCap RangeCap { get; set; }
        internal double Angle { get; set; }

        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
                if (circularGaugeWebAssemblyObject == null)
                    return;
                this.ApplyInitialSettingsToWebAssemblyObject();
            }
        }

        private object circularPointer;
        public CircularPointer()
        {
            //circularPointer = OpenSilver.Interop.ExecuteJavaScript("new Object()");
        }

        private void ApplyInitialSettingsToWebAssemblyObject()
        {
            this.LoadCircularPointerFromWebAssemblyControl();

            OpenSilver.Interop.ExecuteJavaScript("$0.value = $1", this.circularPointer, this.Value);
            OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(this.SymbolPointerStroke));

            if (this.PointerType == PointerType.RangePointer)
            {
                OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(this.RangePointerStroke));
                OpenSilver.Interop.ExecuteJavaScript("$0.pointerWidth = $1", this.circularPointer, this.RangePointerStrokeThickness);
            }
            if (this.PointerType == PointerType.NeedlePointer)
            {
                OpenSilver.Interop.ExecuteJavaScript("$0.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(this.NeedlePointerStroke));
                OpenSilver.Interop.ExecuteJavaScript("$0.cap.color = $1", this.circularPointer, SfCircularGauge.BrushToRgbaString(this.NeedlePointerStroke));
                OpenSilver.Interop.ExecuteJavaScript("$0.pointerWidth = $1", this.circularPointer, this.NeedlePointerStrokeThickness);
            }

            OpenSilver.Interop.ExecuteJavaScript("$0.type = $1", this.circularPointer, this.PointerTypeString);
            OpenSilver.Interop.ExecuteJavaScript("$0.radius = '100%'", this.circularPointer);
            //OpenSilver.Interop.ExecuteJavaScript("$0.animation = {enable: true, duration: 100}", this.circularPointer);
            OpenSilver.Interop.ExecuteJavaScript("$0.animation = {enable: false}", this.circularPointer);

            this.SaveCircularPointerToWebAssemblyControl();
        }

        /// <summary>
        /// Method used to Load pointer object that is currently being used in WebAssembly Runtime
        /// </summary>
        private void LoadCircularPointerFromWebAssemblyControl()
        {
            this.circularPointer = OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].pointers[$1]", this.CircularGaugeWebAssemblyObject, this.PointerIndex);
        }

        /// <summary>
        /// Method used to replace current pointer object with new one
        /// Used when trying to go back to default properties.
        /// </summary>
        private void SaveCircularPointerToWebAssemblyControl()
        {
            OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].pointers[$1] = $2", this.CircularGaugeWebAssemblyObject, this.PointerIndex, this.circularPointer);
        }
    }
    public class CircularPointerCollection : ObservableCollection<CircularPointer>
    {
        private bool isControlLoaded = false;
        internal object circularScale = null;
        internal object circularPointerCollection = null;

        //internal CircularPointer mainPointer = null;
        #region Properties
        
        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
                if (circularGaugeWebAssemblyObject == null)
                    return;

                //isControlLoaded = true;
                //if(this.mainPointer != null)
                //    this.mainPointer.CircularGaugeWebAssemblyObject = circularGaugeWebAssemblyObject;

                for (int i = 0; i < this.Count; i++)
                    this.Items[i].CircularGaugeWebAssemblyObject = circularGaugeWebAssemblyObject;
            }
        }

        #endregion
        
        public CircularPointerCollection()
        {
            this.circularPointerCollection = OpenSilver.Interop.ExecuteJavaScript("[]");
        }


        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);

            if (e.Action != NotifyCollectionChangedAction.Add)
                return;
            
            for (int i = 0; i < this.Count; i++)            
                this.Items[i].PointerIndex = i;            
        }

    }

    public class GaugeAnnotation
    {
        public double Angle { get; set; }
        public double Offset { get; set; }
        public ViewAlignment HorizontalPosition { get; set; }
        public ViewAlignment VerticalPosition { get; set; }
       
        #region Properties

        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
            }
        }

        private object content;
        /// <summary>
        /// Content is a TextBlock object
        /// </summary>
        public object Content
        {
            get { return content; }
            set
            {
                this.content = value;
            }
        }

        private Brush contentColor = new SolidColorBrush(Color.FromArgb(0xff, 0x00, 0x00, 0x90));
        public Brush ContentColor
        {
            get { return contentColor; }
            set
            {
                this.contentColor = value;
                //}
            }
        }

        private Point viewMargin = new Point(0, 0); 
        public Point ViewMargin 
        { 
            get => viewMargin;
            set
            {
                viewMargin = value;                
            }
        }

 

        #endregion

        public GaugeAnnotation()
        {
        }
    }
    public class CircularGaugeAnnotationCollection : ObservableCollection<GaugeAnnotation>
    {
        private bool isControlLoaded = false;
        internal object circularAnnotationCollection = null;

        #region Properties

        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
                if (circularGaugeWebAssemblyObject == null)
                    return;

                isControlLoaded = true;

                for (int i = 0; i < this.Count; i++)                
                    this.Items[i].CircularGaugeWebAssemblyObject = circularGaugeWebAssemblyObject;                
            }
        }
      
        #endregion

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);

            if (e.Action != NotifyCollectionChangedAction.Add)
                return;

            for (int i = 0; i < this.Count; i++)
            {
                this.Items[i].CircularGaugeWebAssemblyObject = this.circularGaugeWebAssemblyObject;
            }
        }

        public CircularGaugeAnnotationCollection()
        {
            this.circularAnnotationCollection = OpenSilver.Interop.ExecuteJavaScript("[]");
        }


    }

    public class CircularPanel
    {
        public double ScaleMaxWidth { get; set; }
        public SweepDirection SweepDirection { get; set; }
        public double SweepAngle { get; set; }
        public double StartAngle { get; set; }
        public double AngularSpace { get; set; }
        public Brush Foreground { get; set; }
        public CircularScaleLabelCollection Labels { get; set; }
    }

    public class TickSetting
    {
        public double Length { get; set; }
        public double Offset { get; set; }
        public double StartOffset { get; set; }
        public double EndOffset { get; set; }
        public Brush Stroke { get; set; }
        public double StrokeThickness { get; set; }
    }
    public class MajorTickSetting : TickSetting
    {
    }
    public class MinorTickSetting : TickSetting
    { 
    }

    public class TickLinesPanel
    {
        public SweepDirection SweepDirection { get; set; }
        public double SweepAngle { get; set; }
        public double StartAngle { get; set; }
        public double MinorTickAngularSpace { get; set; }
        public double MajorTickAngularSpace { get; set; }
        public double Interval { get; set; }
        public bool IsLogarithmic { get; set; } = false;
        public int LogBase { get; set; }
        public double StartValue { get; set; } = 0;
        public double EndValue { get; set; } = 100;
        public double HalfLength { get; set; }
        public double Length { get; set; }
        public Brush Stroke { get; set; }
        public double TickStrokeThickness { get; set; }
        public CircularScaleTickCollection Ticks { get; set; }
    }

    public class CircularScale
    {
        private const double DEFAULT_MAJOR_TICK_HEIGHT = 12;
        private const double DEFAULT_MAJOR_TICK_WIDTH = 3;
        private const double DEFAULT_MINOR_TICK_HEIGHT = 6;
        private const double DEFAULT_MINOR_TICK_WIDTH = 2;

        internal object circularScale = null;

        private bool IsReady
        {
            get { return circularScale != null && CircularGaugeWebAssemblyObject != null; }
        }

        private object circularGaugeWebAssemblyObject;
        /// <summary>
        /// Reference to the Circular Gauge control 
        /// </summary>
        public object CircularGaugeWebAssemblyObject 
        {
            get => circularGaugeWebAssemblyObject;
            set
            {
                circularGaugeWebAssemblyObject = value;
                if (circularGaugeWebAssemblyObject == null)
                    return;

                this.ApplyInitialSettingsToWebAssemblyObject();
                this.Pointers.CircularGaugeWebAssemblyObject = circularGaugeWebAssemblyObject;
                this.Ranges.CircularGaugeWebAssemblyObject = circularGaugeWebAssemblyObject;
            } 
        }

        public Brush Background  { get; set; }

        public PathSegmentCollection ScaleSeg { get; set; }

        private bool showTicks = true;
        public bool ShowTicks
        {
            get => showTicks;
            set
            {
                showTicks = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                if (this.IsReady)
                {
                    if(!showTicks)
                    {
                        OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].majorTicks.width = $1", this.CircularGaugeWebAssemblyObject, 0);
                        OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].majorTicks.height = $1", this.CircularGaugeWebAssemblyObject, 0);

                        OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].minorTicks.width = $1", this.CircularGaugeWebAssemblyObject, 0);
                        OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].minorTicks.height = $1", this.CircularGaugeWebAssemblyObject, 0);
                        return;
                    }
                }
            }
        }

        private bool showLabels = true;
        public bool ShowLabels 
        {
            get => showLabels;
            set
            {
                showLabels = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                if (this.IsReady)
                {
                    double size = 0;
                    if (showLabels)
                        size = 8;
                    OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].labelStyle.font.size = $1", this.CircularGaugeWebAssemblyObject, size + "px" );
                }
            }
        }
        
        public bool ShowRim { get; set; }
        public Brush RimStroke { get; set; } = new SolidColorBrush(Color.FromArgb(0x3E, 0x6C, 0x00, 0x00));

        private double rimStrokeThickness = 5;
        public double RimStrokeThickness
        {
            get { return rimStrokeThickness; }
            set
            {
                rimStrokeThickness = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.lineStyle.width = $1", this.circularScale, rimStrokeThickness);
                OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].startWidth = $1", this.circularScale, rimStrokeThickness);
                OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].endWidth = $1", this.circularScale, rimStrokeThickness);

                double offsetTicks = this.RimStrokeThickness / 2;
                OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].majorTicks.offset = $1", this.CircularGaugeWebAssemblyObject, offsetTicks);
                OpenSilver.Interop.ExecuteJavaScript("$0.axes[0].minorTicks.offset = $1", this.CircularGaugeWebAssemblyObject, offsetTicks);
            }
        }



        public NumericScaleType NumericScaleType { get; set; }
        public bool EnableSmartLabels { get; set; }
        public int NoOfFractionalDigit { get; set;  }
        public LabelPosition LabelPosition { get; set; }
        public double LabelOffset { get; set; }
        public TickPosition TickPosition { get; set; }
        public double MajorTickOffset { get; set; }
        public double MinorTickOffset { get; set; }
        public RangePointerPosition RangePointerPosition { get; set; }
        public double RangePointerOffset { get; set; }
        public RangePosition RangePosition { get; set; }
        public double RangeOffset { get; set; }
        public double RimStartOffset { get; set; }
        public double RimEndOffset { get; set; }
        public double RadiusFactor { get; set; }
        internal CircularScaleLabelCollection Labels { get; set; }
        public CircularRangeCollection Ranges { get; set; }
        public CircularPointerCollection Pointers { get; set; }
        public SweepDirection SweepDirection { get; set; }

        private double sweepAngle = 270;
        public double SweepAngle
        {
            get => sweepAngle;
            set { sweepAngle = value; } 
        }

        public double EndAngle
        {
            get
            {
                double angle = StartAngle + sweepAngle;
                if (angle > 360)
                    angle -= 360;
                return angle;
            }
        }

        private double startAngle = 135;
        /// <summary>
        /// Sets and gets the start angle of an axis in circular gauge. <br/>
        /// There is an offset of 90º from the WPF Component to WebAssembly that is compensated in this getter
        /// </summary>
        public double StartAngle 
        {
            get => startAngle + 90; 
            set => startAngle = value; 
        }
        public Point EndPoint { get; set; }
        public Point StartPoint { get; set; }
        public bool BindRangeStrokeToLabels { get; set; }
        public bool BindRangeStrokeToTicks { get; set; }
        public double TickLength { get; set; }
        public double SmallTickLength { get; set; }
        public Brush TickStroke { get ; set; }
        public Brush LabelStroke { get; set; }
        public bool LabelAutoSizeChange { get; set; }
        public string LabelPostfix { get; set; }
        public string LabelPrefix { get; set; }
        public Brush SmallTickStroke { get; set; }
        public double TickStrokeThickness { get; set; }
        public double SmallTickStrokeThickness { get; set; }

        private double startValue = 0;
        public double StartValue
        {
            get { return startValue; }
            set
            {
                startValue = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                if (!ShowTicks)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].start = $1", this.circularScale, startValue);
            }
        }

        private double endValue = 100;
        public double EndValue
        {
            get { return endValue; }
            set
            {
                endValue = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                if (!ShowTicks)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].end = $1", this.circularScale, endValue);
            }
        }

        private double interval = 10;
        /// <summary>
        /// In WPF: The Interval property is used to calculate the tick count for a scale.
        /// In HTML5: The Interval sets the interval of values between the tick lines in circular gauge. 
        /// </summary>
        public double Interval
        {
            get { return interval; }
            set
            {
                interval = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                if (!ShowTicks)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.majorTicks.interval = $1", this.circularScale, interval);
            }
        }


        private int minorTicksPerInterval = 1;
        /// <summary>
        /// In WPF this parameters defines the quantity of minor ticks in each interval <br/>
        /// In WebAssembly the control requires the interval to be defined, so we must do some calculations. <br/>
        /// Use Property <b>MinorTicksInterval</b> in the control
        /// </summary>
        public int MinorTicksPerInterval
        {
            get { return minorTicksPerInterval; }
            set
            {
                minorTicksPerInterval = value;
                if (this.CircularGaugeWebAssemblyObject == null)
                    return;

                if (!ShowTicks)
                    return;

                OpenSilver.Interop.ExecuteJavaScript("$0.minorTicks.interval = $1", this.circularScale, MinorTicksInterval);
            }
        }

        public double MinorTicksInterval
        {
            get
            {
                double minorTicksPerInterval = MinorTicksPerInterval;
                if (minorTicksPerInterval <= 1)
                    minorTicksPerInterval = 1;

                return Interval / (minorTicksPerInterval + 1);
            }
        }

        public bool ShowFirstLabel { get; set; }
        public bool ShowLastLabel { get; set;}
        public TickShape TickShape { get; set; }
        public double Radius { get; set; }
        public MajorTickSetting MajorTickSettings { get; set; }
        public MinorTickSetting MinorTickSettings { get; set; }
        public bool IsLogarithmic { get; set; }
        public int LogBase { get; set; }
        public int LogExponentInterval { get; set; }
        public bool IsLogLabelSuperscriptEnabled { get; set; }
        public double FontSize { get; set; } = 10;

        public CircularScale()
        {
            this.Pointers = new CircularPointerCollection();
            this.Pointers.circularScale = this.circularScale;
            this.Ranges = new CircularRangeCollection(circularScale);
        }

        public void UpdateColors()
        {
            if (this.circularScale == null)
                return;

            Color c = (Color)WK.GetThemeColor((int)WK.eThemeColor.TextForeground);
            string colorStr = $"#{c.R:X2}{c.G:X2}{c.B:X2}";
            OpenSilver.Interop.ExecuteJavaScript("$0.labelStyle.font.color = $1", this.circularScale, colorStr);
        }

        private void ApplyInitialSettingsToWebAssemblyObject()
        {
            if (this.CircularGaugeWebAssemblyObject == null)
                return;
      
            this.circularScale = OpenSilver.Interop.ExecuteJavaScript("$0.axes[0]", this.CircularGaugeWebAssemblyObject);

            OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].start = $1", this.circularScale, this.StartValue);
            OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].end = $1", this.circularScale, this.EndValue);
            OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].radius = '100%'", this.circularScale);
            OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].startWidth = $1", this.circularScale, this.RimStrokeThickness);
            OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].endWidth = $1", this.circularScale, this.RimStrokeThickness);
            OpenSilver.Interop.ExecuteJavaScript("$0.ranges[0].color = $1", this.circularScale, SfCircularGauge.BrushToRgbaString(this.RimStroke));
           
            OpenSilver.Interop.ExecuteJavaScript("$0.lineStyle.width = $1", this.circularScale, this.RimStrokeThickness);
            OpenSilver.Interop.ExecuteJavaScript("$0.lineStyle.color = 'transparent'", this.circularScale);

            double labelSize = 0;
            if (ShowLabels)
                labelSize = this.FontSize;
            
            string labelPosition = "Inside";
            if (this.LabelPosition == LabelPosition.Outside)
                labelPosition = "Outside";

            Color c = (Color)WK.GetThemeColor((int)WK.eThemeColor.TextForeground);
            string colorStr = $"#{c.R:X2}{c.G:X2}{c.B:X2}";

            OpenSilver.Interop.ExecuteJavaScript("$0.labelStyle = {font: {size: $1, fontFamily: 'inherit', color:$2 }, position: $3, offset: -5}", this.circularScale, labelSize + "px", colorStr, labelPosition);

            double offsetTicks = this.RimStrokeThickness / 2;
            double majorLen = this.MajorTickSettings?.Length ?? 0;
            double minorLen = this.MinorTickSettings?.Length ?? 0;

            if (!showTicks)
            {
                OpenSilver.Interop.ExecuteJavaScript("$0.majorTicks.width = 0", this.circularScale);
                OpenSilver.Interop.ExecuteJavaScript("$0.majorTicks.height = 0", this.circularScale);
                OpenSilver.Interop.ExecuteJavaScript("$0.minorTicks.width = 0", this.circularScale);
                OpenSilver.Interop.ExecuteJavaScript("$0.minorTicks.height = 0", this.circularScale);
            }
            else
            {
                OpenSilver.Interop.ExecuteJavaScript("$0.majorTicks = {interval: $1, offset: $2}", this.circularScale, this.Interval, offsetTicks);
                OpenSilver.Interop.ExecuteJavaScript("$0.minorTicks = {interval: $1, offset: $2}", this.circularScale, this.MinorTicksInterval, offsetTicks);


                if (majorLen > 0)
                {
                    OpenSilver.Interop.ExecuteJavaScript("$0.majorTicks.height = $1", this.circularScale, majorLen);
                }
                if (minorLen > 0)
                {
                    OpenSilver.Interop.ExecuteJavaScript("$0.minorTicks.height = $1", this.circularScale, minorLen);
                }
            }

            OpenSilver.Interop.ExecuteJavaScript("$0.minimum = $1", this.circularScale, this.StartValue);
            OpenSilver.Interop.ExecuteJavaScript("$0.maximum =  $1", this.circularScale, this.EndValue);
            OpenSilver.Interop.ExecuteJavaScript("$0.startAngle = $1", this.circularScale, TConvert.ToString(this.StartAngle));
            OpenSilver.Interop.ExecuteJavaScript("$0.endAngle =  $1", this.circularScale, TConvert.ToString(this.EndAngle));

            OpenSilver.Interop.ExecuteJavaScript("$0.axes[0] = $1", this.CircularGaugeWebAssemblyObject, this.circularScale);
        }

    }

    #endregion 

}


In this section...

The root page @parent could not be found in space FrameworX 10.1.

  • No labels