Skip to main content

CHART_CSS

Constant CHART_CSS 

Source
pub const CHART_CSS: &str = "/* forge-charts \u{2014} default stylesheet.\n *\n * Consumers inject this once at the app root via\n *     <Stylesheet text=forge_charts::CHART_CSS />\n *\n * Theme via CSS variables on `:root` or any parent of `.charts-root`.\n * Per-series colors come from `--charts-series-<color_class>` /\n * `--charts-series-<color_class>-soft` (the soft variant fades into\n * the gradient stop). The defaults below give a clean ApexCharts-\n * inspired blue/teal palette; override per-app as needed.\n */\n\n:root {\n  --charts-fg: rgb(17 24 39);\n  --charts-fg-muted: rgb(107 114 128);\n  --charts-fg-faint: rgb(156 163 175);\n  --charts-grid-color: rgba(17, 24, 39, 0.06);\n  --charts-bg: transparent;\n\n  /* Default series palette. Add more --charts-series-<name>* pairs\n   * in the consumer app to extend. */\n  --charts-series-opened: rgb(43, 127, 255);\n  --charts-series-opened-soft: rgba(142, 197, 255, 0.55);\n  --charts-series-closed: rgb(34, 197, 94);\n  --charts-series-closed-soft: rgba(134, 239, 172, 0.55);\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --charts-fg: rgb(229 231 235);\n    --charts-fg-muted: rgb(156 163 175);\n    --charts-fg-faint: rgb(107 114 128);\n    --charts-grid-color: rgba(229, 231, 235, 0.08);\n  }\n}\n\n/* ---------- outer container ---------- */\n\n.charts-root {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  /* X-axis strip geometry \u{2014} ONE source of truth. The tick-label row\n   * (`--charts-x-axis-label`) plus the gap above it\n   * (`--charts-x-axis-gap`) make up the strip footprint\n   * (`--charts-x-axis-height`). The Y-tick scale spacer and the\n   * crosshair/dots overlays all offset by that footprint so the plot\'s\n   * 0%..100% lines up with the SVG baseline. Previously these were four\n   * separate `26px`/`22px`/`4px` literals with \"keep in sync\" comments;\n   * deriving them here means a consumer can retune the axis in one place\n   * and nothing can drift. */\n  --charts-x-axis-gap: 4px;\n  --charts-x-axis-label: 22px;\n  --charts-x-axis-height: calc(var(--charts-x-axis-label) + var(--charts-x-axis-gap));\n  /* Set by the AreaChart component via inline style:\n   *   --charts-height: 320px;\n   * Falls back to 320px if not provided.\n   *\n   * Use a *fixed* height (not min-height) so the legend + plot live\n   * inside a bounded box. With min-height the SVG\'s intrinsic\n   * viewBox aspect ratio can drive the column taller than its track\n   * and visually bleed into the legend above it. */\n  height: var(--charts-height, 320px);\n  color: var(--charts-fg);\n  background: var(--charts-bg);\n  font-family: inherit;\n  font-size: 12px;\n  position: relative;\n}\n\n.charts-empty {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex: 1;\n  color: var(--charts-fg-faint);\n  font-style: italic;\n}\n\n/* ---------- legend ---------- */\n\n.charts-legend {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  /* Wider horizontal gap so adjacent legend items feel like distinct\n   * controls instead of one run-on row; row-gap keeps wrapped lines\n   * from sticking together when the chart is narrow. */\n  column-gap: 28px;\n  row-gap: 8px;\n  padding: 6px 8px 14px 8px;\n  color: var(--charts-fg-muted);\n}\n.charts-legend-item {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n}\n/* Hidden series (toggled off via a name click): dim the whole item and\n * strike the label so it\'s obviously excluded from the plot. */\n.charts-legend-item.is-hidden {\n  opacity: 0.4;\n}\n.charts-legend-item.is-hidden .charts-legend-name {\n  text-decoration: line-through;\n}\n/* The swatch (dot + hidden color input) is the color-picker affordance;\n * the name beside it is the show/hide toggle. Keeping them separate is\n * what lets a name click mean \"hide\" instead of \"open picker\". */\n.charts-legend-swatch {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  cursor: pointer;\n}\n.charts-legend-name {\n  cursor: pointer;\n}\n.charts-legend-dot {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  border-radius: 999px;\n  background: currentColor;\n}\n.charts-legend-name {\n  color: var(--charts-fg);\n  font-size: 12.5px;\n}\n/* The swatch is a <label> wrapping a hidden color input on top of the\n * dot. Clicking the dot opens the native picker; the dot stays the\n * visible affordance. `appearance: none` plus sizing the input to match\n * the dot keeps the picker invisible on Chromium + Firefox + Safari. */\n.charts-legend-item {\n  user-select: none;\n}\n.charts-legend-swatch:hover .charts-legend-dot {\n  transform: scale(1.15);\n  transition: transform 120ms ease-out;\n}\n.charts-legend-color-input {\n  position: absolute;\n  left: 0;\n  top: 50%;\n  transform: translateY(-50%);\n  width: 10px;\n  height: 10px;\n  padding: 0;\n  margin: 0;\n  border: none;\n  outline: none;\n  background: transparent;\n  opacity: 0;\n  cursor: pointer;\n  -webkit-appearance: none;\n  appearance: none;\n}\n.charts-legend-color-input::-webkit-color-swatch-wrapper { padding: 0; }\n.charts-legend-color-input::-webkit-color-swatch { border: none; }\n\n/* ---------- plot area ---------- */\n\n.charts-plot {\n  /* Y-axis gutter is a CSS var so consumers with wide tick labels\n   * (durations like \"33m 20s\", byte counts) can widen it without\n   * forking the stylesheet. Default fits ~5 digits. */\n  display: grid;\n  grid-template-columns: var(--charts-y-gutter, 52px) 1fr;\n  flex: 1;\n  min-height: 0;\n  position: relative;\n}\n\n.charts-y-axis {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n}\n/* The scale holds the tick labels and is sized to the SVG (flex:1).\n * The ::after spacer matches the x-axis strip footprint\n * (`--charts-x-axis-height`) so the scale\'s 0%..100% lines up with the\n * plot\'s top edge and baseline. */\n.charts-y-axis-scale {\n  position: relative;\n  flex: 1 1 0;\n  min-height: 0;\n}\n.charts-y-axis::after {\n  content: \"\";\n  display: block;\n  flex: 0 0 var(--charts-x-axis-height);\n}\n.charts-y-tick {\n  position: absolute;\n  right: 8px;\n  transform: translateY(-50%);\n  color: var(--charts-fg-muted);\n  font-size: 11px;\n  font-variant-numeric: tabular-nums;\n  white-space: nowrap;\n}\n\n.charts-plot-area {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  /* min-* both zero \u{2014} without these the flex algorithm refuses to\n   * shrink the SVG below its intrinsic 800x280 box and we end up\n   * overflowing the parent row in either axis. */\n  min-width: 0;\n  min-height: 0;\n}\n.charts-svg {\n  /* Explicit basis of 0 + min-height: 0 lets the flex algorithm size\n   * the SVG strictly to its track. Without `min-height: 0` an SVG\n   * with a viewBox falls back to its intrinsic aspect-ratio height\n   * and bleeds out of the plot area. `overflow: visible` lets hover\n   * dots near the bottom render as full circles instead of being\n   * clipped against the X axis \u{2014} the sizing rule above is what\n   * actually keeps the bleed honest, not clipping. */\n  flex: 1 1 0;\n  min-height: 0;\n  width: 100%;\n  display: block;\n  overflow: visible;\n}\n.charts-x-axis {\n  position: relative;\n  height: var(--charts-x-axis-label);\n  margin-top: var(--charts-x-axis-gap);\n}\n.charts-x-tick {\n  position: absolute;\n  transform: translateX(-50%);\n  color: var(--charts-fg-muted);\n  font-size: 11px;\n  white-space: nowrap;\n}\n\n/* ---------- gridlines ---------- */\n\n.charts-grid-line {\n  stroke: var(--charts-grid-color);\n  stroke-width: 1;\n  vector-effect: non-scaling-stroke;\n}\n\n/* ---------- series + animations ---------- */\n\n/*\n * Animation strategy: SVG `d` attributes can\'t be CSS-interpolated,\n * so we don\'t try to morph paths on data change. Instead each\n * series group `scaleY` animates from 0\u{2192}1 against the chart\'s\n * bottom, giving the \"rise from baseline\" reveal that reads as a\n * polished intro (and replays on the next data change because the\n * group\'s key signature, derived from the data, shifts under it).\n *\n * Honor `prefers-reduced-motion`: skip the transform animation,\n * keep the opacity fade so the chart still appears smoothly.\n */\n@keyframes charts-series-rise {\n  from {\n    transform: scaleY(0);\n    opacity: 0;\n  }\n  to {\n    transform: scaleY(1);\n    opacity: 1;\n  }\n}\n@keyframes charts-fade-in {\n  from { opacity: 0; }\n  to { opacity: 1; }\n}\n\n.charts-series-paths {\n  transform-origin: 0 100%;\n  animation: charts-series-rise 650ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n/* Stagger the second series slightly so they feel layered rather\n * than rising in lockstep. Generalize to per-series if we ever ship\n * more than ~3 series. */\n.charts-series-paths:nth-child(2) { animation-delay: 80ms; }\n.charts-series-paths:nth-child(3) { animation-delay: 160ms; }\n\n.charts-y-axis,\n.charts-x-axis,\n.charts-grid {\n  animation: charts-fade-in 500ms ease-out both;\n  animation-delay: 100ms;\n}\n.charts-legend {\n  animation: charts-fade-in 400ms ease-out both;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .charts-series-paths,\n  .charts-y-axis,\n  .charts-x-axis,\n  .charts-grid,\n  .charts-legend {\n    animation: none;\n  }\n}\n\n.charts-area {\n  /* fill via the <linearGradient id=\"charts-grad-<color>\"> ref */\n  stroke: none;\n  opacity: 1;\n  /* Phase B will toggle opacity on hover; transition primes the\n   * change to be smooth. */\n  transition: opacity 180ms ease-out;\n}\n.charts-line {\n  stroke-width: 2;\n  vector-effect: non-scaling-stroke;\n  fill: none;\n  transition: stroke-width 180ms ease-out;\n}\n\n/* ---------- crosshair + hover dots (Phase B) ---------- */\n\n/* Crosshairs live in this HTML overlay (not as SVG lines) so WebKit\'s\n * SVG stacking quirk can\'t paint a spike\'s filled area over them. Bounds\n * match the dots overlay (and the SVG) so `left:%` maps 1:1. Sits below\n * the dots overlay in document order \u{2192} line under dot, above everything\n * else. */\n.charts-crosshair-overlay {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  /* Offset above the x-axis strip so percent positions map to the plot. */\n  bottom: var(--charts-x-axis-height);\n  pointer-events: none;\n}\n.charts-crosshair {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  width: 0;\n  border-left: 1px dashed var(--charts-fg-faint);\n  opacity: 0.85;\n}\n/* Pinned crosshair (set by a click): heavier + more opaque so the\n * parked reference reads as distinct from the live cursor. */\n.charts-crosshair-pinned {\n  border-left-width: 1.5px;\n  border-left-color: var(--charts-fg-muted);\n  opacity: 0.95;\n}\n/* Hover dots live as HTML divs in this overlay rather than as SVG\n * circles. The SVG above uses `preserveAspectRatio=\"none\"` to stretch\n * paths across whatever container width it gets \u{2014} that stretching\n * turned circles into ovals. Rendering dots in HTML/pixel space keeps\n * them round regardless of the chart\'s aspect ratio. The overlay sits\n * over the plot region only (offset above the x-axis) so percent\n * positions map 1:1 to the SVG viewBox underneath. */\n.charts-dots-overlay {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  /* Offset above the x-axis strip so percent positions map to the plot. */\n  bottom: var(--charts-x-axis-height);\n  pointer-events: none;\n}\n.charts-dot {\n  position: absolute;\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  transform: translate(-50%, -50%);\n  pointer-events: none;\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22);\n}\n.charts-dot.charts-series-opened {\n  background: var(--charts-series-opened);\n}\n.charts-dot.charts-series-closed {\n  background: var(--charts-series-closed);\n}\n\n/* ---------- tooltip card (Phase B) ----------\n * Translucent, rounded, soft shadow. Content is consumer-provided via\n * the `tooltip` prop closure; the crate provides the chrome only. */\n.charts-tooltip {\n  position: absolute;\n  pointer-events: none;\n  z-index: 5;\n  min-width: 200px;\n  padding: 10px 12px;\n  background: rgba(20, 22, 28, 0.92);\n  color: rgb(229 231 235);\n  border-radius: 10px;\n  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.22);\n  font-size: 12px;\n  line-height: 1.5;\n  backdrop-filter: blur(8px);\n  -webkit-backdrop-filter: blur(8px);\n  animation: charts-fade-in 140ms ease-out both;\n}\n@media (prefers-color-scheme: light) {\n  .charts-tooltip {\n    background: rgba(255, 255, 255, 0.96);\n    color: rgb(17 24 39);\n    box-shadow: 0 8px 22px rgba(15, 23, 42, 0.16);\n  }\n}\n/* Pinned tooltip (parked by a click, shown on every synced chart at the\n * pinned instant). Thin accent ring distinguishes it from the live one,\n * and `min-width` is relaxed since several can be on screen at once. */\n.charts-tooltip-pinned {\n  border: 1px solid var(--charts-fg-faint);\n  min-width: 0;\n  animation: none;\n}\n\n/* Default tooltip row styling (consumer markup uses these classes\n * via the bundled `tooltip_for` example or their own card). */\n.charts-tooltip-card {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n}\n.charts-tooltip-date {\n  font-weight: 600;\n  margin-bottom: 4px;\n  color: inherit;\n}\n.charts-tooltip-row {\n  display: grid;\n  grid-template-columns: auto 1fr auto;\n  align-items: center;\n  gap: 6px;\n}\n.charts-tooltip-row.sub {\n  padding-left: 16px;\n  opacity: 0.75;\n  font-size: 11px;\n}\n.charts-tooltip-dot {\n  display: inline-block;\n  width: 8px;\n  height: 8px;\n  border-radius: 999px;\n  background: currentColor;\n}\n.charts-tooltip-dot.charts-series-opened {\n  color: var(--charts-series-opened);\n}\n.charts-tooltip-dot.charts-series-closed {\n  color: var(--charts-series-closed);\n}\n.charts-tooltip-label {\n  color: inherit;\n}\n.charts-tooltip-value {\n  font-variant-numeric: tabular-nums;\n  font-weight: 500;\n}\n\n/* Per-series color hooks. Add a CSS rule per series in the consumer\n * app for any color_class beyond the bundled defaults. */\n.charts-line.charts-series-opened {\n  stroke: var(--charts-series-opened);\n}\n.charts-line.charts-series-closed {\n  stroke: var(--charts-series-closed);\n}\n.charts-legend-item:has(.charts-series-opened) .charts-legend-dot {\n  color: var(--charts-series-opened);\n}\n.charts-legend-item:has(.charts-series-closed) .charts-legend-dot {\n  color: var(--charts-series-closed);\n}\n\n/* Gradient stops \u{2014} referenced by <stop> inside <linearGradient>. */\n.charts-gradient.charts-series-opened .charts-gradient-top {\n  stop-color: var(--charts-series-opened-soft);\n  stop-opacity: 0.85;\n}\n.charts-gradient.charts-series-opened .charts-gradient-bottom {\n  stop-color: var(--charts-series-opened-soft);\n  stop-opacity: 0.05;\n}\n.charts-gradient.charts-series-closed .charts-gradient-top {\n  stop-color: var(--charts-series-closed-soft);\n  stop-opacity: 0.85;\n}\n.charts-gradient.charts-series-closed .charts-gradient-bottom {\n  stop-color: var(--charts-series-closed-soft);\n  stop-opacity: 0.05;\n}\n\n/* ---------- Phase C: drag-to-zoom band + reset pill ---------- */\n\n/* Semi-transparent rectangle drawn while the user drags a selection\n * across the plot. Drawn in SVG viewBox units so it stretches with\n * the chart; `pointer-events: none` keeps the drag itself live on the\n * underlying plot area instead of being intercepted by the band. */\n.charts-zoom-band {\n  fill: rgba(66, 98, 255, 0.12);\n  stroke: rgba(66, 98, 255, 0.55);\n  stroke-width: 1;\n  stroke-dasharray: 3 2;\n  pointer-events: none;\n  vector-effect: non-scaling-stroke;\n}\n\n@media (prefers-color-scheme: dark) {\n  .charts-zoom-band {\n    fill: rgba(120, 150, 255, 0.18);\n    stroke: rgba(120, 150, 255, 0.7);\n  }\n}\n\n/* Reset-zoom pill \u{2014} absolute-positioned top-right of the plot area.\n * Only mounted when a zoom is active. Mirrors the tooltip card\'s\n * translucent visual language. */\n.charts-zoom-reset {\n  position: absolute;\n  top: 8px;\n  right: 8px;\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  padding: 4px 10px;\n  font: inherit;\n  font-size: 11px;\n  font-weight: 500;\n  color: var(--charts-fg);\n  background: rgba(255, 255, 255, 0.88);\n  border: 1px solid rgba(17, 24, 39, 0.12);\n  border-radius: 999px;\n  box-shadow: 0 1px 2px rgba(17, 24, 39, 0.06);\n  cursor: pointer;\n  backdrop-filter: blur(8px);\n  -webkit-backdrop-filter: blur(8px);\n  transition: background-color 120ms ease, box-shadow 120ms ease;\n}\n.charts-zoom-reset:hover {\n  background: rgba(255, 255, 255, 1);\n  box-shadow: 0 2px 6px rgba(17, 24, 39, 0.1);\n}\n.charts-zoom-reset:focus-visible {\n  outline: 2px solid var(--charts-series-opened);\n  outline-offset: 2px;\n}\n\n@media (prefers-color-scheme: dark) {\n  .charts-zoom-reset {\n    background: rgba(31, 41, 55, 0.85);\n    border-color: rgba(229, 231, 235, 0.18);\n    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);\n  }\n  .charts-zoom-reset:hover {\n    background: rgba(31, 41, 55, 1);\n  }\n}\n";
Expand description

Default stylesheet bundled with the crate. Inject once at the app root via <Stylesheet text=CHART_CSS /> (Leptos meta).