poetry-charts 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +6 -0
  3. data/CHANGELOG.md +3 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +94 -0
  6. data/THIRD_PARTY_NOTICES.md +39 -0
  7. data/app/assets/stylesheets/poetry-charts.css +117 -0
  8. data/app/components/poetry/charts/adapter_chart/component.html.erb +6 -0
  9. data/app/components/poetry/charts/adapter_chart/component.rb +99 -0
  10. data/app/components/poetry/charts/adapter_chart/preview.rb +37 -0
  11. data/app/components/poetry/charts/adapter_chart/style.rb +16 -0
  12. data/app/components/poetry/charts/area_chart/component.html.erb +75 -0
  13. data/app/components/poetry/charts/area_chart/component.rb +251 -0
  14. data/app/components/poetry/charts/area_chart/preview.rb +95 -0
  15. data/app/components/poetry/charts/area_chart/style.rb +29 -0
  16. data/app/components/poetry/charts/bar_chart/component.html.erb +95 -0
  17. data/app/components/poetry/charts/bar_chart/component.rb +318 -0
  18. data/app/components/poetry/charts/bar_chart/preview.rb +115 -0
  19. data/app/components/poetry/charts/bar_chart/style.rb +28 -0
  20. data/app/components/poetry/charts/bar_math.rb +95 -0
  21. data/app/components/poetry/charts/cartesian_family.rb +105 -0
  22. data/app/components/poetry/charts/chart_family.rb +58 -0
  23. data/app/components/poetry/charts/composed_chart/component.html.erb +95 -0
  24. data/app/components/poetry/charts/composed_chart/component.rb +309 -0
  25. data/app/components/poetry/charts/composed_chart/preview.rb +62 -0
  26. data/app/components/poetry/charts/composed_chart/style.rb +24 -0
  27. data/app/components/poetry/charts/container/component.html.erb +4 -0
  28. data/app/components/poetry/charts/container/component.rb +80 -0
  29. data/app/components/poetry/charts/container/preview.rb +45 -0
  30. data/app/components/poetry/charts/container/style.rb +22 -0
  31. data/app/components/poetry/charts/error_bars.rb +35 -0
  32. data/app/components/poetry/charts/legend_content/component.html.erb +10 -0
  33. data/app/components/poetry/charts/legend_content/component.rb +122 -0
  34. data/app/components/poetry/charts/legend_content/preview.rb +23 -0
  35. data/app/components/poetry/charts/legend_content/style.rb +26 -0
  36. data/app/components/poetry/charts/line_chart/component.html.erb +85 -0
  37. data/app/components/poetry/charts/line_chart/component.rb +252 -0
  38. data/app/components/poetry/charts/line_chart/preview.rb +115 -0
  39. data/app/components/poetry/charts/line_chart/style.rb +28 -0
  40. data/app/components/poetry/charts/live.rb +296 -0
  41. data/app/components/poetry/charts/motion.rb +98 -0
  42. data/app/components/poetry/charts/pie_chart/component.html.erb +36 -0
  43. data/app/components/poetry/charts/pie_chart/component.rb +256 -0
  44. data/app/components/poetry/charts/pie_chart/preview.rb +100 -0
  45. data/app/components/poetry/charts/pie_chart/style.rb +19 -0
  46. data/app/components/poetry/charts/polar_family.rb +95 -0
  47. data/app/components/poetry/charts/radar_chart/component.html.erb +73 -0
  48. data/app/components/poetry/charts/radar_chart/component.rb +329 -0
  49. data/app/components/poetry/charts/radar_chart/preview.rb +97 -0
  50. data/app/components/poetry/charts/radar_chart/style.rb +21 -0
  51. data/app/components/poetry/charts/radial_bar_chart/component.html.erb +65 -0
  52. data/app/components/poetry/charts/radial_bar_chart/component.rb +424 -0
  53. data/app/components/poetry/charts/radial_bar_chart/preview.rb +99 -0
  54. data/app/components/poetry/charts/radial_bar_chart/style.rb +27 -0
  55. data/app/components/poetry/charts/reference_marks.rb +156 -0
  56. data/app/components/poetry/charts/scatter_chart/component.html.erb +55 -0
  57. data/app/components/poetry/charts/scatter_chart/component.rb +396 -0
  58. data/app/components/poetry/charts/scatter_chart/preview.rb +93 -0
  59. data/app/components/poetry/charts/scatter_chart/style.rb +22 -0
  60. data/app/components/poetry/charts/tooltip_content/component.html.erb +19 -0
  61. data/app/components/poetry/charts/tooltip_content/component.rb +168 -0
  62. data/app/components/poetry/charts/tooltip_content/preview.rb +39 -0
  63. data/app/components/poetry/charts/tooltip_content/style.rb +31 -0
  64. data/app/components/poetry/charts/tooltip_layer/component.html.erb +6 -0
  65. data/app/components/poetry/charts/tooltip_layer/component.rb +61 -0
  66. data/app/components/poetry/charts/tooltip_layer/style.rb +13 -0
  67. data/app/components/poetry/charts/tooltip_wiring.rb +167 -0
  68. data/app/helpers/poetry/charts/components_helper.rb +125 -0
  69. data/app/javascript/poetry/charts/adapter_controller.js +75 -0
  70. data/app/javascript/poetry/charts/adapter_registry.js +52 -0
  71. data/app/javascript/poetry/charts/adapters/chartjs.js +86 -0
  72. data/app/javascript/poetry/charts/d3.js +14 -0
  73. data/app/javascript/poetry/charts/index.js +67 -0
  74. data/app/javascript/poetry/charts/live/renderer.js +456 -0
  75. data/app/javascript/poetry/charts/live_controller.js +195 -0
  76. data/app/javascript/poetry/charts/motion/flip.js +169 -0
  77. data/app/javascript/poetry/charts/motion/sector.js +62 -0
  78. data/app/javascript/poetry/charts/motion/tween.js +105 -0
  79. data/app/javascript/poetry/charts/motion_controller.js +230 -0
  80. data/app/javascript/poetry/charts/tooltip_controller.js +331 -0
  81. data/app/javascript/poetry/charts/window_controller.js +195 -0
  82. data/config/component_descriptions.yml +14 -0
  83. data/config/component_registry.yml +3055 -0
  84. data/config/controllers_manifest.json +100 -0
  85. data/config/importmap.rb +10 -0
  86. data/lib/poetry/charts/cartesian.rb +277 -0
  87. data/lib/poetry/charts/config.rb +149 -0
  88. data/lib/poetry/charts/engine.rb +76 -0
  89. data/lib/poetry/charts/geometry/area.rb +68 -0
  90. data/lib/poetry/charts/geometry/curve.rb +327 -0
  91. data/lib/poetry/charts/geometry/line.rb +64 -0
  92. data/lib/poetry/charts/geometry/nice_ticks.rb +220 -0
  93. data/lib/poetry/charts/geometry/path.rb +96 -0
  94. data/lib/poetry/charts/geometry/scale/band.rb +81 -0
  95. data/lib/poetry/charts/geometry/scale/linear.rb +127 -0
  96. data/lib/poetry/charts/geometry/stack.rb +129 -0
  97. data/lib/poetry/charts/geometry/ticks.rb +103 -0
  98. data/lib/poetry/charts/geometry.rb +66 -0
  99. data/lib/poetry/charts/polar.rb +179 -0
  100. data/lib/poetry/charts/spec.rb +108 -0
  101. data/lib/poetry/charts/theme_style.rb +53 -0
  102. data/lib/poetry/charts/version.rb +8 -0
  103. data/lib/poetry/charts.rb +114 -0
  104. data/lib/poetry-charts.rb +4 -0
  105. data/themes/default.css +146 -0
  106. data/themes/luma.css +139 -0
  107. data/themes/lyra.css +140 -0
  108. data/themes/maia.css +139 -0
  109. data/themes/mira.css +139 -0
  110. data/themes/nova.css +139 -0
  111. data/themes/rhea.css +139 -0
  112. data/themes/sera.css +140 -0
  113. data/themes/vega.css +144 -0
  114. data/vendor/d3-kernel/LICENSE-d3.txt +17 -0
  115. data/vendor/d3-kernel/LICENSE-decimal.js-light.txt +25 -0
  116. data/vendor/d3-kernel/LICENSE-recharts.txt +26 -0
  117. data/vendor/d3-kernel/README.md +30 -0
  118. data/vendor/d3-kernel/build.mjs +66 -0
  119. data/vendor/d3-kernel/entry.js +27 -0
  120. data/vendor/d3-kernel/recharts/state/cartesianAxisSlice.ts +6 -0
  121. data/vendor/d3-kernel/recharts/util/scale/getNiceTickValues.ts +310 -0
  122. data/vendor/d3-kernel/recharts/util/scale/util/arithmetic.ts +54 -0
  123. data/vendor/d3-kernel/recharts/util/types.ts +5 -0
  124. metadata +186 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Charts
5
+ # The chart-spec: the CLOSED, VERSIONED description every poetry chart
6
+ # compiles to. The server engine consumes it Ruby-side; swappable
7
+ # adapters consume the same spec JSON-side through the duck-typed
8
+ # protocol (render(el, spec) / update / destroy). No engine-specific key
9
+ # ever enters this schema - engine styling lives in the adapter,
10
+ # declared; a pass-through options bag would tie call sites to one
11
+ # engine and the spec could never close.
12
+ #
13
+ # Keys ride the wire camelCased (dataKey, stackId) - one vocabulary
14
+ # on both sides of the seam.
15
+ #
16
+ # @example Compile a spec and serialize it for an adapter
17
+ # Poetry::Charts::Spec.new(
18
+ # type: :bar, data: rows, series: [{ data_key: :revenue }]
19
+ # ).to_json
20
+ class Spec
21
+ # The spec schema version stamped into every payload.
22
+ VERSION = 1
23
+
24
+ # The chart types the spec can describe.
25
+ TYPES = %i[area bar line pie radar radial].freeze
26
+
27
+ # The closed key set a series entry may carry.
28
+ SERIES_KEYS = %i[key data_key name_key stack curve].freeze
29
+ # The closed key set an axis entry may carry.
30
+ AXIS_KEYS = %i[data_key hide tick_count format].freeze
31
+
32
+ attr_reader :type, :data, :series, :axes, :config
33
+
34
+ # @param type [Symbol, String] one of TYPES
35
+ # @param data [Array<Hash>] the rows, one hash per data point
36
+ # @param series [Array<Hash>] series entries; data_key: is required
37
+ # @param axes [Hash] per-axis settings (SERIES_KEYS/AXIS_KEYS closed sets)
38
+ # @param config [Config, Hash, nil] label/color config, wrapped via Config.wrap
39
+ def initialize(type:, data:, series:, axes: {}, config: nil)
40
+ @type = type.to_sym
41
+ unless TYPES.include?(@type)
42
+ raise ArgumentError,
43
+ "unknown chart type #{type.inspect} (one of #{TYPES.join(", ")})"
44
+ end
45
+
46
+ @data = Array(data)
47
+ @series = normalize_series(series)
48
+ @axes = normalize_axes(axes)
49
+ @config = Config.wrap(config)
50
+ end
51
+
52
+ # The wire form: string keys, camelCased entry keys, the version
53
+ # stamped in.
54
+ #
55
+ # @return [Hash]
56
+ def to_h
57
+ {
58
+ "version" => VERSION,
59
+ "type" => type.to_s,
60
+ "data" => data.map { |row| row.to_h.transform_keys(&:to_s) },
61
+ "series" => series.map { |entry| camelize_keys(entry) },
62
+ "axes" => axes.to_h { |name, axis| [name.to_s, camelize_keys(axis)] },
63
+ "config" => config.to_h.transform_values { |value| camelize_keys(value) }
64
+ }
65
+ end
66
+
67
+ # The wire form serialized - what the spec <script> embeds.
68
+ #
69
+ # @return [String]
70
+ def to_json(...)
71
+ to_h.to_json(...)
72
+ end
73
+
74
+ private
75
+
76
+ def normalize_series(series)
77
+ Array(series).map do |entry|
78
+ entry = entry.symbolize_keys
79
+ raise ArgumentError, "a series entry requires data_key: (got #{entry.inspect})" unless entry[:data_key]
80
+
81
+ unknown = entry.keys - SERIES_KEYS
82
+ raise ArgumentError, "unknown series keys #{unknown.inspect} (the spec is closed)" if unknown.any?
83
+
84
+ { key: entry[:data_key].to_s }.merge(entry).compact
85
+ end
86
+ end
87
+
88
+ def normalize_axes(axes)
89
+ axes.symbolize_keys.to_h do |name, axis|
90
+ axis = axis.symbolize_keys
91
+ unknown = axis.keys - AXIS_KEYS
92
+ raise ArgumentError, "unknown axis keys #{unknown.inspect} on #{name} (the spec is closed)" if unknown.any?
93
+
94
+ [name, axis]
95
+ end
96
+ end
97
+
98
+ # snake_case Ruby -> camelCase wire (dataKey, stackId-style).
99
+ # Symbol values (data keys, curve names) become strings on the
100
+ # wire.
101
+ def camelize_keys(hash)
102
+ hash.to_h do |key, value|
103
+ [key.to_s.camelize(:lower), value.is_a?(Symbol) ? value.to_s : value]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Charts
5
+ # Turns a chart's Config into the scoped per-series custom
6
+ # properties, one block per theme -
7
+ #
8
+ # [data-chart=chart-revenue] { --color-desktop: var(--chart-1); }
9
+ # .dark [data-chart=chart-revenue] { --color-desktop: oklch(...); }
10
+ #
11
+ # so series markup (SVG fills, tooltip indicators, legend swatches) can
12
+ # reference var(--color-<key>) and follow theme flips with ZERO
13
+ # re-render. Emission is safe by construction: Config validated every
14
+ # key and color at wrap time.
15
+ #
16
+ # @example
17
+ # Poetry::Charts::ThemeStyle.new(id: "chart-revenue", config: config).css
18
+ class ThemeStyle
19
+ # Theme name -> selector prefix (the .dark class convention).
20
+ THEMES = { light: "", dark: ".dark " }.freeze
21
+
22
+ # @param id [String] the container's data-chart identifier
23
+ # @param config [Config, Hash] the chart config the colors come from
24
+ def initialize(id:, config:)
25
+ @id = id
26
+ @config = Config.wrap(config)
27
+ end
28
+
29
+ # The stylesheet text, or nil when no entry carries a color - a
30
+ # colorless config renders no <style> element at all.
31
+ #
32
+ # @return [String, nil]
33
+ def css
34
+ entries = @config.color_entries
35
+ return nil if entries.empty?
36
+
37
+ THEMES.filter_map { |theme, prefix| theme_block(theme, prefix, entries) }.join("\n")
38
+ end
39
+
40
+ private
41
+
42
+ def theme_block(theme, prefix, entries)
43
+ declarations = entries.filter_map do |entry|
44
+ color = entry.color_for(theme)
45
+ " --color-#{entry.key}: #{color};" if color
46
+ end
47
+ return nil if declarations.empty?
48
+
49
+ "#{prefix}[data-chart=#{@id}] {\n#{declarations.join("\n")}\n}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Charts
5
+ # The gem version.
6
+ VERSION = "0.0.2"
7
+ end
8
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "poetry/core"
4
+ require "yaml"
5
+ require_relative "charts/version"
6
+ require_relative "charts/config"
7
+ require_relative "charts/theme_style"
8
+ require_relative "charts/spec"
9
+ require_relative "charts/geometry"
10
+ require_relative "charts/cartesian"
11
+ require_relative "charts/polar"
12
+
13
+ # The poetry component family's shared root namespace.
14
+ module Poetry
15
+ # poetry's chart tier: charts as server-rendered SVG. Ruby runs the
16
+ # whole geometry pipeline - data -> domains -> scales -> ticks ->
17
+ # points -> paths, with decimal-exact nice ticks - and the finished
18
+ # chart ships in the initial HTML: no-JS/print/email valid, themed by
19
+ # CSS variables (--chart-1..5 + per-chart --color-<key>), dark mode
20
+ # with zero re-render. Stimulus chrome adds tooltip/legend/active
21
+ # interactivity by reading SERVER-EMBEDDED coordinates - no chart math
22
+ # in the browser.
23
+ #
24
+ # Engines stay swappable (three doors): the container contract is
25
+ # engine-agnostic; every chart also compiles to a closed, VERSIONED
26
+ # chart-spec consumed by duck-typed adapters (render/update/destroy -
27
+ # a canvas adapter ships as the reference); client-rendered chart
28
+ # libraries remain reachable through a Stimulus-mounted island.
29
+ #
30
+ # @example Render a chart through the dispatcher helper
31
+ # poetry_chart :bar, data: rows, series: [{ data_key: :revenue }]
32
+ module Charts
33
+ # The curve interpolation whitelist every series slot validates
34
+ # against.
35
+ CURVES = %i[natural linear step step_before step_after monotone_x].freeze
36
+ # The axis capture shape the cartesian family slots accumulate into
37
+ # (scatter carries its own - both axes numeric, different fields).
38
+ AxisConfig = Data.define(:data_key, :tick_formatter, :tick_margin, :tick_count)
39
+ # The grid capture shape - which rule directions render (radar
40
+ # carries its own, with polygon/circle rings).
41
+ GridConfig = Data.define(:vertical, :horizontal)
42
+
43
+ class << self
44
+ # Gem root (the directory containing lib/, app/, config/).
45
+ def root
46
+ @root ||= Pathname.new(File.expand_path("../..", __dir__))
47
+ end
48
+
49
+ # The registry builder this gem commits from (the poetry-ui shared-
50
+ # builder rule: rake registry:generate/verify and the sync test share
51
+ # ONE construction). helper_args carries each poetry_* helper's max
52
+ # positional arity from its real signature - poetry_chart(type, ...)
53
+ # legitimately takes one, which is exactly why arity is an emitted
54
+ # per-helper fact and never a convention.
55
+ def registry
56
+ # The helpers section carries the dispatcher's yields declaration:
57
+ # poetry_chart(type) routes to a component that yields its slot
58
+ # builder, so its block param is legitimate - the one exception to
59
+ # the no-wrapper-yields invariant.
60
+ Poetry::Core::Registry.new(
61
+ source_root: root, helper_args: registry_helper_args,
62
+ helpers: { "poetry_chart" => { "yields" => "the dispatched chart component" } },
63
+ descriptions: registry_descriptions
64
+ )
65
+ end
66
+
67
+ # The editorial per-chart descriptions merged into the registry
68
+ # (component_path => one-liner, from config/component_descriptions.yml).
69
+ # Absent file -> nil, so the registry stays lint-identical without it.
70
+ def registry_descriptions
71
+ path = root.join("config/component_descriptions.yml")
72
+ path.exist? ? YAML.safe_load_file(path) : nil
73
+ end
74
+
75
+ # The installable-item projection, boot-free from the COMMITTED
76
+ # registry - the docs site aggregates this with poetry-ui's for
77
+ # /r/*.json.
78
+ def registry_items
79
+ Poetry::Core::RegistryItems.new(
80
+ registry: YAML.safe_load_file(root.join(Poetry::Core::Registry::RELATIVE_PATH)),
81
+ root: root, gem_name: "poetry-charts", gem_version: VERSION
82
+ )
83
+ end
84
+
85
+ # Parameter kinds that count toward a helper's positional arity.
86
+ POSITIONAL_PARAM_KINDS = %i[req opt].freeze
87
+
88
+ # Each poetry_* helper's max positional arity, read from its real
89
+ # signature.
90
+ # @api private
91
+ def registry_helper_args
92
+ require root.join("app/helpers/poetry/charts/components_helper.rb")
93
+ ComponentsHelper.public_instance_methods(false).grep(/\Apoetry_/).sort.filter_map do |name|
94
+ params = ComponentsHelper.instance_method(name).parameters
95
+ next if params.any? { |kind, _param| kind == :rest }
96
+
97
+ [name.to_s, params.count { |kind, _param| POSITIONAL_PARAM_KINDS.include?(kind) }]
98
+ end.to_h
99
+ end
100
+
101
+ # The tooltip display string shared by every chart family (matches
102
+ # TooltipContent's Row: delimited numerics from RAW values so
103
+ # integers stay integers, verbatim strings, nil for missing).
104
+ def display_value(value)
105
+ return nil if value.nil?
106
+ return ActiveSupport::NumberHelper.number_to_delimited(value) if value.is_a?(Numeric)
107
+
108
+ value.to_s
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ require_relative "charts/engine"
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Bundler auto-require entrypoint for the hyphenated gem name.
4
+ require "poetry/charts"
@@ -0,0 +1,146 @@
1
+ /* poetry-charts default theme — the cn-* named-class layer.
2
+ *
3
+ * Same contract as poetry-ui's themes/default.css: imported layer(base),
4
+ * dictionaries emit the names + structural classes, this file carries the
5
+ * design. The chart chrome names are SHARED across families (one
6
+ * .cn-chart-grid-line serves every cartesian family) — the equivalent of
7
+ * upstream's [&_.recharts-*] override chains, landed on parts poetry owns.
8
+ * Series fills/strokes stay var(--color-<key>) ATTRIBUTES, never classes;
9
+ * the --chart-1..5 palette rides tokens, not this file.
10
+ */
11
+
12
+ /* MARK: Tooltip */
13
+ .cn-chart-tooltip {
14
+ @apply gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl;
15
+ }
16
+
17
+ .cn-chart-tooltip-label {
18
+ @apply font-medium;
19
+ }
20
+
21
+ .cn-chart-tooltip-row {
22
+ @apply gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground;
23
+ }
24
+
25
+ .cn-chart-tooltip-name {
26
+ @apply text-muted-foreground;
27
+ }
28
+
29
+ .cn-chart-tooltip-value {
30
+ @apply font-mono font-medium text-foreground tabular-nums;
31
+ }
32
+
33
+ /* MARK: Legend */
34
+ .cn-chart-legend {
35
+ @apply gap-4;
36
+ }
37
+
38
+ .cn-chart-legend-align-top {
39
+ @apply pb-3;
40
+ }
41
+
42
+ .cn-chart-legend-align-bottom {
43
+ @apply pt-3;
44
+ }
45
+
46
+ .cn-chart-legend-item {
47
+ @apply gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground;
48
+ }
49
+
50
+ .cn-chart-legend-swatch {
51
+ @apply h-2 w-2 rounded-[2px];
52
+ }
53
+
54
+ /* MARK: Cartesian chrome (shared across families) */
55
+ .cn-chart-grid-line {
56
+ @apply stroke-border/50;
57
+ }
58
+
59
+ .cn-chart-tick {
60
+ @apply fill-muted-foreground;
61
+ }
62
+
63
+ .cn-chart-cursor-line {
64
+ @apply stroke-border;
65
+ }
66
+
67
+ .cn-chart-cursor-band {
68
+ @apply fill-muted;
69
+ }
70
+
71
+ .cn-chart-reference-line {
72
+ @apply stroke-muted-foreground;
73
+ }
74
+
75
+ .cn-chart-reference-area {
76
+ @apply fill-muted-foreground;
77
+ }
78
+
79
+ .cn-chart-reference-dot {
80
+ @apply fill-background stroke-muted-foreground;
81
+ }
82
+
83
+ .cn-chart-error-bar {
84
+ @apply stroke-foreground;
85
+ }
86
+
87
+ .cn-chart-brush-track {
88
+ @apply fill-muted;
89
+ }
90
+
91
+ .cn-chart-brush-window {
92
+ @apply fill-foreground/10 stroke-border;
93
+ }
94
+
95
+ .cn-chart-brush-handle {
96
+ @apply fill-muted-foreground;
97
+ }
98
+
99
+ .cn-chart-label {
100
+ @apply fill-foreground;
101
+ }
102
+
103
+ /* MARK: Polar chrome */
104
+ .cn-chart-polar-grid-line {
105
+ @apply fill-none stroke-border;
106
+ }
107
+
108
+ .cn-chart-polar-grid-circle {
109
+ @apply fill-none stroke-border/50;
110
+ }
111
+
112
+ .cn-chart-radial-track {
113
+ @apply fill-muted;
114
+ }
115
+
116
+ .cn-chart-radial-disc-muted {
117
+ @apply fill-muted;
118
+ }
119
+
120
+ .cn-chart-radial-disc-background {
121
+ @apply fill-background;
122
+ }
123
+
124
+ .cn-chart-inside-label {
125
+ @apply fill-background text-xs;
126
+ }
127
+
128
+ .cn-chart-radial-inside-label {
129
+ @apply fill-white capitalize mix-blend-luminosity text-[11px];
130
+ }
131
+
132
+ .cn-chart-center-title {
133
+ @apply fill-foreground text-3xl font-bold;
134
+ }
135
+
136
+ .cn-chart-center-title-radial {
137
+ @apply fill-foreground text-4xl font-bold;
138
+ }
139
+
140
+ .cn-chart-center-title-compact {
141
+ @apply fill-foreground text-2xl font-bold;
142
+ }
143
+
144
+ .cn-chart-center-subtitle {
145
+ @apply fill-muted-foreground;
146
+ }
data/themes/luma.css ADDED
@@ -0,0 +1,139 @@
1
+ /* poetry-charts luma theme - chart chrome under upstream
2
+ * style-luma.css (pinned clone 1773ecfe, the shadcn@4.19.0 release). Upstream ships ONE chart rule (cn-chart-tooltip); luma reskins it exactly as rhea does (popover surface, ring-for-border, rounded-xl) - everything else carries the default treatment.
3
+ * Same contract as default.css: imported layer(base) by the host entry;
4
+ * POETRY_THEME=luma drives the gates and golden set. */
5
+ /* MARK: Tooltip */
6
+ .cn-chart-tooltip {
7
+ @apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 gap-1.5 rounded-xl px-2.5 py-1.5 text-xs shadow-lg ring-1;
8
+ }
9
+
10
+ .cn-chart-tooltip-label {
11
+ @apply font-medium;
12
+ }
13
+
14
+ .cn-chart-tooltip-row {
15
+ @apply gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground;
16
+ }
17
+
18
+ .cn-chart-tooltip-name {
19
+ @apply text-muted-foreground;
20
+ }
21
+
22
+ .cn-chart-tooltip-value {
23
+ @apply font-mono font-medium text-foreground tabular-nums;
24
+ }
25
+
26
+ /* MARK: Legend */
27
+ .cn-chart-legend {
28
+ @apply gap-4;
29
+ }
30
+
31
+ .cn-chart-legend-align-top {
32
+ @apply pb-3;
33
+ }
34
+
35
+ .cn-chart-legend-align-bottom {
36
+ @apply pt-3;
37
+ }
38
+
39
+ .cn-chart-legend-item {
40
+ @apply gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground;
41
+ }
42
+
43
+ .cn-chart-legend-swatch {
44
+ @apply h-2 w-2 rounded-[2px];
45
+ }
46
+
47
+ /* MARK: Cartesian chrome (shared across families) */
48
+ .cn-chart-grid-line {
49
+ @apply stroke-border/50;
50
+ }
51
+
52
+ .cn-chart-tick {
53
+ @apply fill-muted-foreground;
54
+ }
55
+
56
+ .cn-chart-cursor-line {
57
+ @apply stroke-border;
58
+ }
59
+
60
+ .cn-chart-cursor-band {
61
+ @apply fill-muted;
62
+ }
63
+
64
+ .cn-chart-reference-line {
65
+ @apply stroke-muted-foreground;
66
+ }
67
+
68
+ .cn-chart-reference-area {
69
+ @apply fill-muted-foreground;
70
+ }
71
+
72
+ .cn-chart-reference-dot {
73
+ @apply fill-background stroke-muted-foreground;
74
+ }
75
+
76
+ .cn-chart-error-bar {
77
+ @apply stroke-foreground;
78
+ }
79
+
80
+ .cn-chart-brush-track {
81
+ @apply fill-muted;
82
+ }
83
+
84
+ .cn-chart-brush-window {
85
+ @apply fill-foreground/10 stroke-border;
86
+ }
87
+
88
+ .cn-chart-brush-handle {
89
+ @apply fill-muted-foreground;
90
+ }
91
+
92
+ .cn-chart-label {
93
+ @apply fill-foreground;
94
+ }
95
+
96
+ /* MARK: Polar chrome */
97
+ .cn-chart-polar-grid-line {
98
+ @apply fill-none stroke-border;
99
+ }
100
+
101
+ .cn-chart-polar-grid-circle {
102
+ @apply fill-none stroke-border/50;
103
+ }
104
+
105
+ .cn-chart-radial-track {
106
+ @apply fill-muted;
107
+ }
108
+
109
+ .cn-chart-radial-disc-muted {
110
+ @apply fill-muted;
111
+ }
112
+
113
+ .cn-chart-radial-disc-background {
114
+ @apply fill-background;
115
+ }
116
+
117
+ .cn-chart-inside-label {
118
+ @apply fill-background text-xs;
119
+ }
120
+
121
+ .cn-chart-radial-inside-label {
122
+ @apply fill-white capitalize mix-blend-luminosity text-[11px];
123
+ }
124
+
125
+ .cn-chart-center-title {
126
+ @apply fill-foreground text-3xl font-bold;
127
+ }
128
+
129
+ .cn-chart-center-title-radial {
130
+ @apply fill-foreground text-4xl font-bold;
131
+ }
132
+
133
+ .cn-chart-center-title-compact {
134
+ @apply fill-foreground text-2xl font-bold;
135
+ }
136
+
137
+ .cn-chart-center-subtitle {
138
+ @apply fill-muted-foreground;
139
+ }