Skip to content

Commit 27bb9ea

Browse files
authored
Merge pull request #632 from peterboccia/master
fix(data-labels-style): fixed serializations of DataLabels.Style.Colors
2 parents 8101600 + e5bc583 commit 27bb9ea

File tree

5 files changed

+219
-1
lines changed

5 files changed

+219
-1
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<DemoContainer>
2+
<ApexChart TItem="SupportIncident"
3+
Title="Incident Severity"
4+
Options="options">
5+
6+
@foreach (var source in incidents.GroupBy(e => e.Source).OrderBy(e => e.Key))
7+
{
8+
<ApexPointSeries TItem="SupportIncident"
9+
Items="source.OrderBy(e=>e.WeekNumber)"
10+
Name="@source.Key.ToString()"
11+
SeriesType="SeriesType.Heatmap"
12+
Color="@GetColor(source.Key)"
13+
ShowDataLabels="true"
14+
XValue="@(e => e.WeekName)"
15+
YAggregate="@(e => (int)e.Average(a=>a.Severity))" />
16+
}
17+
18+
</ApexChart>
19+
</DemoContainer>
20+
21+
@code {
22+
private List<SupportIncident> incidents { get; set; } = SampleData.GetSupportIncidents();
23+
private ApexChartOptions<SupportIncident> options = new ApexChartOptions<SupportIncident>
24+
{
25+
DataLabels = new DataLabels
26+
{
27+
Style = new DataLabelsStyle
28+
{
29+
Colors = ["function(opts) { return (opts.series[opts.seriesIndex][opts.dataPointIndex] < 30) ? '#00f' : '#f00'; }"],
30+
},
31+
},
32+
};
33+
34+
private string GetColor(IncidentSource source)
35+
{
36+
switch (source)
37+
{
38+
case IncidentSource.Internal:
39+
return "#FF0000";
40+
case IncidentSource.ThirdParty:
41+
return "#0000FF";
42+
case IncidentSource.Integration:
43+
return "#FF00FF";
44+
default:
45+
return "#008FFB";
46+
}
47+
}
48+
}

docs/BlazorApexCharts.Docs/Components/ChartTypes/HeatmapCharts/HeatmapCharts.razor

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
</Snippet>
2121
</CodeSnippet>
2222

23+
<CodeSnippet Title="Data Labels Colors" ClassName=@typeof(Colors).ToString()>
24+
<Snippet>
25+
<DataLabelColors />
26+
</Snippet>
27+
</CodeSnippet>
28+
2329
<CodeSnippet Title=NoShade ClassName=@typeof(NoShade).ToString()>
2430
<Snippet>
2531
<NoShade />
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Text.RegularExpressions;
3+
4+
namespace ApexCharts.Internal;
5+
6+
/// <summary>
7+
/// Provides JavaScript related helper utilities for ApexCharts internal processing.
8+
/// </summary>
9+
internal static class ChartUtilities
10+
{
11+
/// <summary>
12+
/// Regex that matches the beginning of a JavaScript function or arrow function (including optional leading comments).
13+
/// </summary>
14+
private static readonly Regex JsFunctionStartRegex = new(
15+
@"^\s*(?:/\*[\s\S]*?\*/\s*|//[^\n]*\n\s*)*(?:async\s+)?(?:
16+
function\b
17+
| \([^)]*\)\s*=> # (args) =>
18+
| [A-Za-z_$][\w$]*\s*=> # identifier =>
19+
)",
20+
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace);
21+
22+
/// <summary>
23+
/// Regex that matches immediately-invoked function expressions (IIFE) using the traditional function keyword.
24+
/// </summary>
25+
private static readonly Regex IifeFunctionRegex = new(
26+
@"^\s*\(\s*(?:async\s+)?function\b",
27+
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
28+
29+
/// <summary>
30+
/// Regex that matches immediately-invoked arrow function expressions.
31+
/// </summary>
32+
private static readonly Regex IifeArrowRegex = new(
33+
@"^\s*\(\s*(?:async\s+)?\([^)]*\)\s*=>",
34+
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
35+
36+
/// <summary>
37+
/// Determines whether the provided string appears to represent a JavaScript function
38+
/// (standard function declaration, arrow function, or IIFE).
39+
/// </summary>
40+
/// <param name="candidate">The string to inspect.</param>
41+
/// <returns>
42+
/// <see langword="true"/> if the string structurally resembles a JavaScript function;
43+
/// otherwise, <see langword="false"/>.
44+
/// </returns>
45+
internal static bool IsJavaScriptFunction(string candidate)
46+
{
47+
if (string.IsNullOrWhiteSpace(candidate))
48+
return false;
49+
50+
// Quick cheap pre-filter: avoid regex if no plausible tokens
51+
if (!(candidate.Contains("function", StringComparison.OrdinalIgnoreCase) ||
52+
candidate.Contains("=>", StringComparison.Ordinal)))
53+
return false;
54+
55+
// Direct structural check at start
56+
if (JsFunctionStartRegex.IsMatch(candidate))
57+
return true;
58+
59+
// IIFE patterns: (function(...) {...})(), (() => {...})(), etc.
60+
if (IifeFunctionRegex.IsMatch(candidate) || IifeArrowRegex.IsMatch(candidate))
61+
return true;
62+
63+
return false;
64+
}
65+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
5+
6+
namespace ApexCharts.Internal;
7+
8+
/// <summary>
9+
/// Converter for lists of strings that detects entries containing a JavaScript function definition
10+
/// (case-insensitive search for the word "function") and serializes those as an object with the "@eval" key
11+
/// so that ApexCharts interprets them as executable functions instead of plain strings.
12+
///
13+
/// Output behavior:
14+
/// - Normal strings are written as JSON string elements inside the array.
15+
/// - Strings representing functions are written as:
16+
/// { "@eval": "function(x) { return x; }" }
17+
///
18+
/// Example:
19+
/// C# input:
20+
/// <code>
21+
/// new List&lt;string&gt;
22+
/// {
23+
/// "Label 1",
24+
/// "function(w) { return w; }",
25+
/// "Another label"
26+
/// };
27+
/// </code>
28+
///
29+
/// JSON output:
30+
/// <code>
31+
/// [
32+
/// "Label 1",
33+
/// { "@eval": "function(w) { return w; }" },
34+
/// "Another label"
35+
/// ]
36+
/// </code>
37+
///
38+
/// Note: Deserialization (Read) is not implemented because this converter is intended only for outgoing
39+
/// serialization to the client.
40+
/// </summary>
41+
internal class ListStringOrFunctionConverter : JsonConverter<List<string>>
42+
{
43+
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(List<string>);
44+
45+
public override List<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
46+
{
47+
throw new NotImplementedException();
48+
}
49+
50+
public override void Write(Utf8JsonWriter writer, List<string> value, JsonSerializerOptions options)
51+
{
52+
writer.WriteStartArray();
53+
54+
foreach (var item in value)
55+
{
56+
if (ChartUtilities.IsJavaScriptFunction(item))
57+
{
58+
writer.WriteStartObject();
59+
writer.WritePropertyName("@eval");
60+
JsonSerializer.Serialize(writer, item, options);
61+
writer.WriteEndObject();
62+
}
63+
else
64+
{
65+
writer.WriteStringValue(item);
66+
}
67+
}
68+
69+
writer.WriteEndArray();
70+
}
71+
}

src/Blazor-ApexCharts/Models/ApexChartOptions.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1856,8 +1856,36 @@ public class DataLabelsBackground
18561856
public class DataLabelsStyle
18571857
{
18581858
/// <summary>
1859-
/// Fore colors for the dataLabels. Accepts an array of string colors (['#333', '#999']) or an array of functions ([function(opts) { return '#333' }]) (Each index in the array corresponds to the series).
1859+
/// Fore colors for the dataLabels (each index in the array corresponds to the series).
18601860
/// </summary>
1861+
/// <remarks>
1862+
/// Accepts:
1863+
/// <list type="bullet">
1864+
/// <item><description>An array of string colors (e.g. <c>{ "#333", "#999" }</c>)</description></item>
1865+
/// <item><description>An array of JavaScript functions serialized as strings (e.g. <c>{ "function(opts) { return '#333' }" }</c>)</description></item>
1866+
/// </list>
1867+
/// Each index in the array corresponds to the series index.
1868+
/// <para>Examples:</para>
1869+
/// <code>
1870+
/// // Using static colors
1871+
/// var style = new DataLabelsStyle
1872+
/// {
1873+
/// Colors = new List&lt;string&gt; { "#333", "#999" }
1874+
/// };
1875+
///
1876+
/// // Using JavaScript functions (strings will be passed through the converter)
1877+
/// var styleWithFunctions = new DataLabelsStyle
1878+
/// {
1879+
/// Colors = new List&lt;string&gt;
1880+
/// {
1881+
/// "function(opts) { return '#333'; }",
1882+
/// "function(opts) { return opts.seriesIndex % 2 ? '#999' : '#555'; }"
1883+
/// }
1884+
/// };
1885+
/// </code>
1886+
/// For short inline snippets use the inline code tag, e.g. <c>"#333"</c>.
1887+
/// </remarks>
1888+
[JsonConverter(typeof(ListStringOrFunctionConverter))]
18611889
public List<string> Colors { get; set; }
18621890

18631891
/// <summary>

0 commit comments

Comments
 (0)