@tekkare/romulus 0.1.7 → 0.1.9

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.
package/dist/module.d.mts CHANGED
@@ -3,9 +3,27 @@ import * as _nuxt_schema from '@nuxt/schema';
3
3
  interface RomulusModuleOptions {
4
4
  /**
5
5
  * Prefix for all Romulus components.
6
- * @default 'R'
6
+ * @default 'Rm'
7
7
  */
8
8
  prefix?: string;
9
+ /**
10
+ * Injecte `base.css`, un reset global (`* { margin: 0 }`, `button { border: none }`,
11
+ * `a { text-decoration: none }`, couleur et fond sur `html`/`body`).
12
+ *
13
+ * A passer a `false` dans une app qui cohabite avec un autre socle CSS
14
+ * (Auriga, une feuille maison) : le reset y repeindrait toutes les pages
15
+ * existantes, pas seulement celles qui utilisent Romulus.
16
+ * @default true
17
+ */
18
+ baseStyles?: boolean;
19
+ /**
20
+ * Injecte `utilities.css`. Les classes ne sont pas prefixees (`.row`,
21
+ * `.column`, `.block`, `.border`, `.absolute`...) et entrent donc en
22
+ * collision avec le CSS d'une app existante. A passer a `false` en
23
+ * cohabitation ; les tokens et les composants restent disponibles.
24
+ * @default true
25
+ */
26
+ utilities?: boolean;
9
27
  }
10
28
  declare const _default: _nuxt_schema.NuxtModule<RomulusModuleOptions, RomulusModuleOptions, false>;
11
29
 
package/dist/module.d.ts CHANGED
@@ -3,9 +3,27 @@ import * as _nuxt_schema from '@nuxt/schema';
3
3
  interface RomulusModuleOptions {
4
4
  /**
5
5
  * Prefix for all Romulus components.
6
- * @default 'R'
6
+ * @default 'Rm'
7
7
  */
8
8
  prefix?: string;
9
+ /**
10
+ * Injecte `base.css`, un reset global (`* { margin: 0 }`, `button { border: none }`,
11
+ * `a { text-decoration: none }`, couleur et fond sur `html`/`body`).
12
+ *
13
+ * A passer a `false` dans une app qui cohabite avec un autre socle CSS
14
+ * (Auriga, une feuille maison) : le reset y repeindrait toutes les pages
15
+ * existantes, pas seulement celles qui utilisent Romulus.
16
+ * @default true
17
+ */
18
+ baseStyles?: boolean;
19
+ /**
20
+ * Injecte `utilities.css`. Les classes ne sont pas prefixees (`.row`,
21
+ * `.column`, `.block`, `.border`, `.absolute`...) et entrent donc en
22
+ * collision avec le CSS d'une app existante. A passer a `false` en
23
+ * cohabitation ; les tokens et les composants restent disponibles.
24
+ * @default true
25
+ */
26
+ utilities?: boolean;
9
27
  }
10
28
  declare const _default: _nuxt_schema.NuxtModule<RomulusModuleOptions, RomulusModuleOptions, false>;
11
29
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.6",
7
+ "version": "0.1.8",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -10,15 +10,19 @@ const module = defineNuxtModule({
10
10
  }
11
11
  },
12
12
  defaults: {
13
- prefix: "Rm"
13
+ prefix: "Rm",
14
+ baseStyles: true,
15
+ utilities: true
14
16
  },
15
17
  async setup(options, nuxt) {
16
18
  const { resolve } = createResolver(import.meta.url);
17
19
  const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
18
20
  nuxt.options.css.push(resolve(runtimeDir, "assets/styles/tokens.css"));
19
- nuxt.options.css.push(resolve(runtimeDir, "assets/styles/base.css"));
21
+ if (options.baseStyles)
22
+ nuxt.options.css.push(resolve(runtimeDir, "assets/styles/base.css"));
20
23
  nuxt.options.css.push(resolve(runtimeDir, "assets/styles/components.css"));
21
- nuxt.options.css.push(resolve(runtimeDir, "assets/styles/utilities.css"));
24
+ if (options.utilities)
25
+ nuxt.options.css.push(resolve(runtimeDir, "assets/styles/utilities.css"));
22
26
  await addComponentsDir({
23
27
  path: resolve(runtimeDir, "components"),
24
28
  prefix: options.prefix
@@ -1 +1 @@
1
- .row{display:flex;flex-direction:row}.column{display:flex;flex-direction:column}.row-reverse{display:flex;flex-direction:row-reverse}.column-reverse{display:flex;flex-direction:column-reverse}.x-start{justify-content:flex-start}.x-center{justify-content:center}.x-end{justify-content:flex-end}.x-between{justify-content:space-between}.x-around{justify-content:space-around}.x-evenly{justify-content:space-evenly}.y-start{align-items:flex-start}.y-center{align-items:center}.y-end{align-items:flex-end}.y-between{align-content:space-between}.y-stretch{align-items:stretch}.y-baseline{align-items:baseline}.wrap{flex-wrap:wrap}.nowrap{flex-wrap:nowrap}.grow{flex-grow:1}.grow-0{flex-grow:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-1{flex:1}.flex-auto{flex:auto}.flex-none{flex:none}.self-start{align-self:flex-start}.self-center{align-self:center}.self-end{align-self:flex-end}.self-stretch{align-self:stretch}.w-full{width:100%}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.min-w-0{min-width:0}.h-full{height:100%}.h-auto{height:auto}.h-screen{height:100vh}.min-h-0{min-height:0}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.sticky{position:sticky}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.block{display:block}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.gap-4{gap:4px}.gap-6{gap:6px}.gap-8{gap:8px}.gap-10{gap:10px}.gap-12{gap:12px}.gap-14{gap:14px}.gap-16{gap:16px}.gap-18{gap:18px}.gap-20{gap:20px}.gap-22{gap:22px}.gap-24{gap:24px}.gap-26{gap:26px}.gap-28{gap:28px}.gap-30{gap:30px}.gap-32{gap:32px}.gap-34{gap:34px}.gap-36{gap:36px}.gap-38{gap:38px}.gap-40{gap:40px}.gap-42{gap:42px}.gap-44{gap:44px}.gap-46{gap:46px}.gap-48{gap:48px}.gap-50{gap:50px}.gap-52{gap:52px}.gap-54{gap:54px}.gap-56{gap:56px}.gap-58{gap:58px}.gap-60{gap:60px}.gap-62{gap:62px}.gap-64{gap:64px}.gap-66{gap:66px}.gap-68{gap:68px}.gap-70{gap:70px}.gap-72{gap:72px}.gap-74{gap:74px}.gap-76{gap:76px}.gap-78{gap:78px}.gap-80{gap:80px}.p-0{padding:0}.p-4{padding:4px}.p-8{padding:8px}.p-12{padding:12px}.p-16{padding:16px}.p-20{padding:20px}.p-24{padding:24px}.p-32{padding:32px}.p-40{padding:40px}.p-48{padding:48px}.px-4{padding-left:4px;padding-right:4px}.px-8{padding-left:8px;padding-right:8px}.px-12{padding-left:12px;padding-right:12px}.px-16{padding-left:16px;padding-right:16px}.px-20{padding-left:20px;padding-right:20px}.px-24{padding-left:24px;padding-right:24px}.px-32{padding-left:32px;padding-right:32px}.py-4{padding-bottom:4px;padding-top:4px}.py-8{padding-bottom:8px;padding-top:8px}.py-12{padding-bottom:12px;padding-top:12px}.py-16{padding-bottom:16px;padding-top:16px}.py-20{padding-bottom:20px;padding-top:20px}.py-24{padding-bottom:24px;padding-top:24px}.py-32{padding-bottom:32px;padding-top:32px}.m-0{margin:0}.m-auto{margin:auto}.mx-auto{margin-right:auto}.ml-auto,.mx-auto{margin-left:auto}.mr-auto{margin-right:auto}.mt-auto{margin-top:auto}.mb-auto{margin-bottom:auto}.border{border:1px solid var(--r-color-gray-200)}.border-t{border-top:1px solid var(--r-color-gray-200)}.border-b{border-bottom:1px solid var(--r-color-gray-200)}.border-l{border-left:1px solid var(--r-color-gray-200)}.border-r{border-right:1px solid var(--r-color-gray-200)}.border-none{border:none}.rounded-sm{border-radius:var(--r-radius-sm)}.rounded-md{border-radius:var(--r-radius-md)}.rounded-lg{border-radius:var(--r-radius-lg)}.rounded-xl{border-radius:var(--r-radius-xl)}.rounded-full{border-radius:var(--r-radius-full)}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}
1
+ .row{display:flex;flex-direction:row}.column{display:flex;flex-direction:column}.row-reverse{display:flex;flex-direction:row-reverse}.column-reverse{display:flex;flex-direction:column-reverse}.x-start{justify-content:flex-start}.x-center{justify-content:center}.x-end{justify-content:flex-end}.x-between{justify-content:space-between}.x-around{justify-content:space-around}.x-evenly{justify-content:space-evenly}.y-start{align-items:flex-start}.y-center{align-items:center}.y-end{align-items:flex-end}.y-between{align-content:space-between}.y-stretch{align-items:stretch}.y-baseline{align-items:baseline}.wrap{flex-wrap:wrap}.nowrap{flex-wrap:nowrap}.grow{flex-grow:1}.grow-0{flex-grow:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-1{flex:1}.flex-auto{flex:auto}.flex-none{flex:none}.self-start{align-self:flex-start}.self-center{align-self:center}.self-end{align-self:flex-end}.self-stretch{align-self:stretch}.w-full{width:100%}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.min-w-0{min-width:0}.h-full{height:100%}.h-auto{height:auto}.h-screen{height:100vh}.min-h-0{min-height:0}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.sticky{position:sticky}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.block{display:block}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.gap-2{gap:var(--space-2)}.gap-4{gap:var(--space-4)}.gap-6{gap:var(--space-6)}.gap-8{gap:var(--space-8)}.gap-12{gap:var(--space-12)}.gap-16{gap:var(--space-16)}.gap-20{gap:var(--space-20)}.gap-24{gap:var(--space-24)}.gap-32{gap:var(--space-32)}.gap-40{gap:var(--space-40)}.gap-48{gap:var(--space-48)}.p-0{padding:0}.p-2{padding:var(--space-2)}.p-4{padding:var(--space-4)}.p-6{padding:var(--space-6)}.p-8{padding:var(--space-8)}.p-12{padding:var(--space-12)}.p-16{padding:var(--space-16)}.p-20{padding:var(--space-20)}.p-24{padding:var(--space-24)}.p-32{padding:var(--space-32)}.p-40{padding:var(--space-40)}.p-48{padding:var(--space-48)}.px-2{padding-left:var(--space-2);padding-right:var(--space-2)}.px-4{padding-left:var(--space-4);padding-right:var(--space-4)}.px-6{padding-left:var(--space-6);padding-right:var(--space-6)}.px-8{padding-left:var(--space-8);padding-right:var(--space-8)}.px-12{padding-left:var(--space-12);padding-right:var(--space-12)}.px-16{padding-left:var(--space-16);padding-right:var(--space-16)}.px-20{padding-left:var(--space-20);padding-right:var(--space-20)}.px-24{padding-left:var(--space-24);padding-right:var(--space-24)}.px-32{padding-left:var(--space-32);padding-right:var(--space-32)}.px-40{padding-left:var(--space-40);padding-right:var(--space-40)}.px-48{padding-left:var(--space-48);padding-right:var(--space-48)}.py-2{padding-bottom:var(--space-2);padding-top:var(--space-2)}.py-4{padding-bottom:var(--space-4);padding-top:var(--space-4)}.py-6{padding-bottom:var(--space-6);padding-top:var(--space-6)}.py-8{padding-bottom:var(--space-8);padding-top:var(--space-8)}.py-12{padding-bottom:var(--space-12);padding-top:var(--space-12)}.py-16{padding-bottom:var(--space-16);padding-top:var(--space-16)}.py-20{padding-bottom:var(--space-20);padding-top:var(--space-20)}.py-24{padding-bottom:var(--space-24);padding-top:var(--space-24)}.py-32{padding-bottom:var(--space-32);padding-top:var(--space-32)}.py-40{padding-bottom:var(--space-40);padding-top:var(--space-40)}.py-48{padding-bottom:var(--space-48);padding-top:var(--space-48)}.m-0{margin:0}.m-auto{margin:auto}.mx-auto{margin-right:auto}.ml-auto,.mx-auto{margin-left:auto}.mr-auto{margin-right:auto}.mt-auto{margin-top:auto}.mb-auto{margin-bottom:auto}.border{border:1px solid var(--border-default)}.border-t{border-top:1px solid var(--border-default)}.border-b{border-bottom:1px solid var(--border-default)}.border-l{border-left:1px solid var(--border-default)}.border-r{border-right:1px solid var(--border-default)}.border-none{border:none}.rounded-sm{border-radius:var(--radius-sm)}.rounded-md{border-radius:var(--radius-md)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-full{border-radius:var(--radius-pill)}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}
@@ -0,0 +1,45 @@
1
+ <!--
2
+ RmBarChart - bar chart vertical (une ou plusieurs series, empilable) ou
3
+ horizontal. Option construite par utils/chart, palette et theme resolus par
4
+ useChartTheme, donc mode nuit et modes daltoniens suivis automatiquement.
5
+ -->
6
+ <script setup lang="ts">
7
+ import { computed } from 'vue'
8
+ import { useChartTheme } from '../composables/useChartTheme'
9
+ import { rmBarHorizontalOption, rmBarVerticalOption, type RmSeriesInput, RM_CHART_HEIGHTS } from '../utils/chart'
10
+
11
+ const props = withDefaults(defineProps<{
12
+ /** Categories de l'axe : annees, libelles d'indicateur. */
13
+ categories: (string | number)[]
14
+ series: RmSeriesInput[]
15
+ horizontal?: boolean
16
+ height?: keyof typeof RM_CHART_HEIGHTS | number
17
+ description?: string
18
+ /** Formatage de la valeur en bout de barre (horizontal uniquement). */
19
+ labelFormatter?: (value: number) => string
20
+ }>(), {
21
+ horizontal: false,
22
+ height: 'standard',
23
+ description: undefined,
24
+ labelFormatter: undefined,
25
+ })
26
+
27
+ const { theme, palette, decal } = useChartTheme()
28
+
29
+ const option = computed(() =>
30
+ props.horizontal
31
+ ? rmBarHorizontalOption(
32
+ props.categories.map(String),
33
+ props.series[0]?.data ?? [],
34
+ theme.value,
35
+ palette.value.cat,
36
+ decal.value,
37
+ props.labelFormatter,
38
+ )
39
+ : rmBarVerticalOption(props.categories, props.series, theme.value, palette.value.cat, decal.value),
40
+ )
41
+ </script>
42
+
43
+ <template>
44
+ <RmChart :option="option" :height="height" :description="description" />
45
+ </template>
@@ -0,0 +1,75 @@
1
+ <!--
2
+ RmChart - enveloppe ECharts bas niveau. Les composants RmBarChart,
3
+ RmLineChart, RmDonutChart et RmSparklineChart construisent leur `option` et
4
+ la passent ici ; une page peut aussi fournir une option brute pour un type
5
+ non couvert.
6
+
7
+ Renderer SVG impose, comme dans le design system : net en HiDPI, et
8
+ exportable en vectoriel depuis le menu de la chart-card.
9
+ -->
10
+ <script setup lang="ts">
11
+ import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
12
+ import { RM_CHART_HEIGHTS } from '../utils/chart'
13
+
14
+ const props = withDefaults(defineProps<{
15
+ /** Option ECharts complete. */
16
+ option: Record<string, unknown>
17
+ /** Hauteur : un palier normalise du design system, ou un nombre de pixels. */
18
+ height?: keyof typeof RM_CHART_HEIGHTS | number
19
+ /**
20
+ * Description textuelle du graphe pour les lecteurs d'ecran. ECharts genere
21
+ * un resume automatique, mais il enonce des series et des valeurs brutes :
22
+ * une phrase de lecture vaut mieux. Le survol n'etant jamais le seul acces
23
+ * a l'information, la donnee cle doit de toute facon rester dans le texte
24
+ * de l'ecran.
25
+ */
26
+ description?: string
27
+ }>(), {
28
+ height: 'standard',
29
+ description: undefined,
30
+ })
31
+
32
+ const el = ref<HTMLElement | null>(null)
33
+ const chart = shallowRef<{ setOption: (o: unknown, n?: boolean) => void, resize: () => void, dispose: () => void } | null>(null)
34
+ let observer: ResizeObserver | null = null
35
+
36
+ const heightPx = () =>
37
+ typeof props.height === 'number' ? props.height : RM_CHART_HEIGHTS[props.height]
38
+
39
+ onMounted(async () => {
40
+ if (!el.value) return
41
+ const echarts = await import('echarts')
42
+ chart.value = echarts.init(el.value, undefined, { renderer: 'svg' })
43
+ chart.value.setOption(props.option)
44
+
45
+ // ResizeObserver plutot qu'un listener global sur window : un graphe dans un
46
+ // panneau repliable ou une grille change de taille sans que la fenetre bouge.
47
+ observer = new ResizeObserver(() => chart.value?.resize())
48
+ observer.observe(el.value)
49
+ })
50
+
51
+ // `true` en second argument : on remplace l'option au lieu de la fusionner.
52
+ // Sans ca, retirer une serie la laisse affichee, parce qu'ECharts fusionne
53
+ // par defaut et ne sait pas qu'elle a disparu.
54
+ watch(() => props.option, (next) => chart.value?.setOption(next, true), { deep: true })
55
+
56
+ onBeforeUnmount(() => {
57
+ observer?.disconnect()
58
+ observer = null
59
+ chart.value?.dispose()
60
+ chart.value = null
61
+ })
62
+
63
+ defineExpose({ instance: chart })
64
+ </script>
65
+
66
+ <template>
67
+ <figure class="rm-chart">
68
+ <div ref="el" class="echart" :style="{ height: `${heightPx()}px` }" role="img" :aria-label="description" />
69
+ <figcaption v-if="description" class="sr-only">{{ description }}</figcaption>
70
+ </figure>
71
+ </template>
72
+
73
+ <style scoped>
74
+ .rm-chart{margin:0}.echart{width:100%}
75
+ </style>
@@ -34,6 +34,7 @@ const props = withDefaults(defineProps<{
34
34
 
35
35
  const emit = defineEmits<{
36
36
  exportPng: []
37
+ exportSvg: []
37
38
  exportCsv: []
38
39
  exportXlsx: []
39
40
  copyLink: []
@@ -67,7 +68,7 @@ onBeforeUnmount(() => {
67
68
  document.removeEventListener('keydown', onEscape)
68
69
  })
69
70
 
70
- function run(action: 'exportPng' | 'exportCsv' | 'exportXlsx' | 'copyLink' | 'fullscreen') {
71
+ function run(action: 'exportPng' | 'exportSvg' | 'exportCsv' | 'exportXlsx' | 'copyLink' | 'fullscreen') {
71
72
  menuOpen.value = false
72
73
  emit(action)
73
74
  }
@@ -94,6 +95,9 @@ function run(action: 'exportPng' | 'exportCsv' | 'exportXlsx' | 'copyLink' | 'fu
94
95
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportPng')">
95
96
  <RmIcon name="Image" :size="14" /> Image PNG
96
97
  </button>
98
+ <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportSvg')">
99
+ <RmIcon name="VectorThree" :size="14" /> Vectoriel SVG
100
+ </button>
97
101
  <button class="cc-menu-item" type="button" role="menuitem" @click="run('exportCsv')">
98
102
  <RmIcon name="FileCsv" :size="14" /> Données CSV
99
103
  </button>
@@ -0,0 +1,27 @@
1
+ <!--
2
+ RmDonutChart - repartition en parts. Au-dela de six parts, regrouper le
3
+ reste en "Autres" : la palette categorielle s'arrete a six couleurs
4
+ distinguables sous dyschromatopsie.
5
+ -->
6
+ <script setup lang="ts">
7
+ import { computed } from 'vue'
8
+ import { useChartTheme } from '../composables/useChartTheme'
9
+ import { rmDonutOption, RM_CHART_HEIGHTS } from '../utils/chart'
10
+
11
+ const props = withDefaults(defineProps<{
12
+ data: { name: string, value: number }[]
13
+ height?: keyof typeof RM_CHART_HEIGHTS | number
14
+ description?: string
15
+ }>(), {
16
+ height: 'standard',
17
+ description: undefined,
18
+ })
19
+
20
+ const { theme, palette, decal } = useChartTheme()
21
+
22
+ const option = computed(() => rmDonutOption(props.data, theme.value, palette.value.cat, decal.value))
23
+ </script>
24
+
25
+ <template>
26
+ <RmChart :option="option" :height="height" :description="description" />
27
+ </template>
@@ -0,0 +1,42 @@
1
+ <!--
2
+ RmLineChart - courbe, aire, ou marche d'escalier (`step`) pour les series de
3
+ prix et de decisions discretes.
4
+ -->
5
+ <script setup lang="ts">
6
+ import { computed } from 'vue'
7
+ import { useChartTheme } from '../composables/useChartTheme'
8
+ import { rmLineOption, type RmSeriesInput, RM_CHART_HEIGHTS } from '../utils/chart'
9
+
10
+ const props = withDefaults(defineProps<{
11
+ categories: (string | number)[]
12
+ series: RmSeriesInput[]
13
+ /** Remplissage degrade sous la courbe. */
14
+ area?: boolean
15
+ /** Marche d'escalier : prix, taux de remboursement, decisions datees. */
16
+ step?: boolean
17
+ smooth?: boolean
18
+ height?: keyof typeof RM_CHART_HEIGHTS | number
19
+ description?: string
20
+ }>(), {
21
+ area: false,
22
+ step: false,
23
+ smooth: true,
24
+ height: 'standard',
25
+ description: undefined,
26
+ })
27
+
28
+ const { theme, palette, decal } = useChartTheme()
29
+
30
+ const option = computed(() =>
31
+ rmLineOption(props.categories, props.series, theme.value, palette.value.cat, {
32
+ area: props.area,
33
+ step: props.step,
34
+ smooth: props.smooth,
35
+ decal: decal.value,
36
+ }),
37
+ )
38
+ </script>
39
+
40
+ <template>
41
+ <RmChart :option="option" :height="height" :description="description" />
42
+ </template>
@@ -0,0 +1,33 @@
1
+ <!--
2
+ RmSparklineChart - micro-courbe pour une cellule de tableau ou une tuile KPI.
3
+ Sans axes ni tooltip : elle donne une forme, pas une valeur. La valeur doit
4
+ rester lisible en texte a cote, le survol n'etant jamais le seul acces a
5
+ l'information.
6
+ -->
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+ import { useChartTheme } from '../composables/useChartTheme'
10
+ import { rmSparklineOption, RM_CHART_HEIGHTS } from '../utils/chart'
11
+
12
+ const props = withDefaults(defineProps<{
13
+ values: (number | null)[]
14
+ /** Couleur explicite ; par defaut la primaire de la palette active. */
15
+ color?: string
16
+ height?: keyof typeof RM_CHART_HEIGHTS | number
17
+ description?: string
18
+ }>(), {
19
+ color: undefined,
20
+ height: 'sparkline',
21
+ description: undefined,
22
+ })
23
+
24
+ // Pas de `theme` ici : une sparkline n'a ni axe ni tooltip a colorer. Le
25
+ // basculement jour/nuit la recalcule quand meme, parce que `palette` en depend.
26
+ const { palette } = useChartTheme()
27
+
28
+ const option = computed(() => rmSparklineOption(props.values, props.color ?? palette.value.primary))
29
+ </script>
30
+
31
+ <template>
32
+ <RmChart :option="option" :height="height" :description="description" />
33
+ </template>
@@ -0,0 +1,7 @@
1
+ export declare function useChartTheme(): {
2
+ night: import("vue").Ref<boolean, boolean>;
3
+ cvd: import("vue").Ref<boolean, boolean>;
4
+ theme: import("vue").ComputedRef<import("../utils/chart").RmChartTheme>;
5
+ palette: import("vue").ComputedRef<import("../utils/chart").RmPalette>;
6
+ decal: import("vue").ComputedRef<boolean>;
7
+ };
@@ -0,0 +1,27 @@
1
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2
+ import { rmChartTheme, rmPalette } from "../utils/chart.js";
3
+ const CVD_CLASSES = ["cvd-deutera", "cvd-protan", "cvd-trita", "cvd-mono"];
4
+ export function useChartTheme() {
5
+ const night = ref(false);
6
+ const cvd = ref(false);
7
+ let observer = null;
8
+ function read() {
9
+ if (typeof document === "undefined") return;
10
+ const list = document.body.classList;
11
+ night.value = list.contains("night");
12
+ cvd.value = CVD_CLASSES.some((c) => list.contains(c));
13
+ }
14
+ onMounted(() => {
15
+ read();
16
+ observer = new MutationObserver(read);
17
+ observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
18
+ });
19
+ onBeforeUnmount(() => {
20
+ observer?.disconnect();
21
+ observer = null;
22
+ });
23
+ const theme = computed(() => rmChartTheme(night.value));
24
+ const palette = computed(() => rmPalette(cvd.value ? "cvdSafe" : "default", night.value));
25
+ const decal = computed(() => cvd.value);
26
+ return { night, cvd, theme, palette, decal };
27
+ }
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Theme et constructeurs d'option ECharts.
3
+ *
4
+ * Porte depuis le design system : palettes et resolveur de `reference.html`
5
+ * (`window.OIP_PALETTES` / `CHART_THEME`), constructeurs d'option de
6
+ * `references/charts.md`. ECharts v5 est la librairie standard de tous les
7
+ * dashboards Tekkare ; ne pas en introduire une seconde.
8
+ *
9
+ * Les couleurs sont ici en litteral et non en `var(--...)` : ECharts peint
10
+ * dans un canvas SVG qu'il genere lui-meme, il ne resout pas les variables
11
+ * CSS. C'est la seule raison acceptee d'ecrire un hex hors tokens.
12
+ */
13
+ export interface RmPalette {
14
+ /** Palette categorielle, 6 couleurs maximum sans ordre. Au-dela, passer en sequentiel ou regrouper en "Autres". */
15
+ cat: string[];
16
+ primary: string;
17
+ accent: string;
18
+ neutral: string;
19
+ /** Palette sequentielle, du plus clair au plus fonce : heatmaps, choroplethes. */
20
+ seq: string[];
21
+ }
22
+ /**
23
+ * Les quatre palettes du design system. `cvdSafe` est la palette Okabe-Ito,
24
+ * validee pour les trois principaux types de dyschromatopsie ; le violet OIP
25
+ * y devient le bleu #0072B2 qui joue le role de couleur primaire.
26
+ */
27
+ export declare const RM_PALETTES: Record<string, RmPalette>;
28
+ /** Palettes par verticale produit, cf. references/charts.md. */
29
+ export declare const RM_PRODUCT_PALETTES: Record<string, string[]>;
30
+ /** Palette divergente : negatif vers neutre vers positif. */
31
+ export declare const RM_DIVERGING: string[];
32
+ export interface RmChartTheme {
33
+ night: boolean;
34
+ textColor: string;
35
+ mutedColor: string;
36
+ dimmedColor: string;
37
+ splitLine: string;
38
+ axisLine: string;
39
+ tooltipBg: string;
40
+ tooltipText: string;
41
+ tooltipBorder: string;
42
+ backgroundColor: string;
43
+ }
44
+ export declare const rmChartTheme: (night: boolean) => RmChartTheme;
45
+ /**
46
+ * Resout la palette active. En mode nuit, la variante *Dark prend le relais
47
+ * quand elle existe : les teintes du mode jour ne contrastent pas assez sur
48
+ * #0E0E14 et une serie devient illisible, ce qui est un bug d'accessibilite
49
+ * et pas un choix esthetique.
50
+ */
51
+ export declare function rmPalette(name?: string, night?: boolean): RmPalette;
52
+ /** Hauteurs normalisees du design system. */
53
+ export declare const RM_CHART_HEIGHTS: {
54
+ readonly sparkline: 48;
55
+ readonly kpi: 80;
56
+ readonly compact: 200;
57
+ readonly standard: 260;
58
+ readonly large: 320;
59
+ readonly full: 360;
60
+ };
61
+ /**
62
+ * Socle commun a tous les graphes. `decal` active le double encodage par
63
+ * motifs : sur un graphe multi-series, la couleur seule ne suffit pas, c'est
64
+ * une regle du design system et non une option.
65
+ */
66
+ export declare function rmBaseOption(theme: RmChartTheme, decal?: boolean): {
67
+ backgroundColor: string;
68
+ animation: boolean;
69
+ aria: {
70
+ enabled: boolean;
71
+ decal: {
72
+ show: boolean;
73
+ };
74
+ };
75
+ textStyle: {
76
+ fontFamily: string;
77
+ };
78
+ grid: {
79
+ left: number;
80
+ right: number;
81
+ top: number;
82
+ bottom: number;
83
+ containLabel: boolean;
84
+ };
85
+ tooltip: {
86
+ trigger: string;
87
+ backgroundColor: string;
88
+ textStyle: {
89
+ color: string;
90
+ fontFamily: string;
91
+ fontSize: number;
92
+ };
93
+ borderColor: string;
94
+ borderWidth: number;
95
+ };
96
+ };
97
+ export interface RmSeriesInput {
98
+ name?: string;
99
+ data: (number | null)[];
100
+ color?: string;
101
+ /** Cle d'empilement : deux series avec la meme cle s'empilent. */
102
+ stack?: string;
103
+ }
104
+ /** Bar chart vertical, une ou plusieurs series, empilable. */
105
+ export declare function rmBarVerticalOption(categories: (string | number)[], series: RmSeriesInput[], theme: RmChartTheme, palette: string[], decal?: boolean): {
106
+ legend: {
107
+ top: number;
108
+ textStyle: {
109
+ color: string;
110
+ fontSize: number;
111
+ fontFamily: string;
112
+ };
113
+ } | undefined;
114
+ xAxis: {
115
+ splitLine: {
116
+ show: boolean;
117
+ };
118
+ axisLabel: {
119
+ fontSize: number;
120
+ color: string;
121
+ fontFamily: string;
122
+ };
123
+ axisLine: {
124
+ lineStyle: {
125
+ color: string;
126
+ };
127
+ };
128
+ type: string;
129
+ data: (string | number)[];
130
+ };
131
+ yAxis: {
132
+ axisLabel: {
133
+ fontSize: number;
134
+ color: string;
135
+ fontFamily: string;
136
+ };
137
+ axisLine: {
138
+ lineStyle: {
139
+ color: string;
140
+ };
141
+ };
142
+ splitLine: {
143
+ lineStyle: {
144
+ color: string;
145
+ };
146
+ };
147
+ type: string;
148
+ };
149
+ series: {
150
+ type: string;
151
+ name: string | undefined;
152
+ stack: string | undefined;
153
+ data: (number | null)[];
154
+ itemStyle: {
155
+ color: string;
156
+ borderRadius: number[];
157
+ };
158
+ }[];
159
+ backgroundColor: string;
160
+ animation: boolean;
161
+ aria: {
162
+ enabled: boolean;
163
+ decal: {
164
+ show: boolean;
165
+ };
166
+ };
167
+ textStyle: {
168
+ fontFamily: string;
169
+ };
170
+ grid: {
171
+ left: number;
172
+ right: number;
173
+ top: number;
174
+ bottom: number;
175
+ containLabel: boolean;
176
+ };
177
+ tooltip: {
178
+ trigger: string;
179
+ backgroundColor: string;
180
+ textStyle: {
181
+ color: string;
182
+ fontFamily: string;
183
+ fontSize: number;
184
+ };
185
+ borderColor: string;
186
+ borderWidth: number;
187
+ };
188
+ };
189
+ /** Bar chart horizontal, avec valeur affichee en bout de barre. */
190
+ export declare function rmBarHorizontalOption(labels: string[], values: (number | null)[], theme: RmChartTheme, palette: string[], decal?: boolean, formatter?: (value: number) => string): {
191
+ grid: {
192
+ left: number;
193
+ right: number;
194
+ top: number;
195
+ bottom: number;
196
+ containLabel: boolean;
197
+ };
198
+ xAxis: {
199
+ axisLabel: {
200
+ fontSize: number;
201
+ color: string;
202
+ fontFamily: string;
203
+ };
204
+ axisLine: {
205
+ lineStyle: {
206
+ color: string;
207
+ };
208
+ };
209
+ splitLine: {
210
+ lineStyle: {
211
+ color: string;
212
+ };
213
+ };
214
+ type: string;
215
+ };
216
+ yAxis: {
217
+ splitLine: {
218
+ show: boolean;
219
+ };
220
+ axisLabel: {
221
+ fontSize: number;
222
+ color: string;
223
+ fontFamily: string;
224
+ };
225
+ axisLine: {
226
+ lineStyle: {
227
+ color: string;
228
+ };
229
+ };
230
+ type: string;
231
+ data: string[];
232
+ };
233
+ series: {
234
+ type: string;
235
+ data: (number | null)[];
236
+ itemStyle: {
237
+ color: (params: {
238
+ dataIndex: number;
239
+ }) => string;
240
+ borderRadius: number[];
241
+ };
242
+ label: {
243
+ show: boolean;
244
+ position: string;
245
+ fontSize: number;
246
+ color: string;
247
+ fontFamily: string;
248
+ formatter: ((p: {
249
+ value: number;
250
+ }) => string) | undefined;
251
+ };
252
+ }[];
253
+ backgroundColor: string;
254
+ animation: boolean;
255
+ aria: {
256
+ enabled: boolean;
257
+ decal: {
258
+ show: boolean;
259
+ };
260
+ };
261
+ textStyle: {
262
+ fontFamily: string;
263
+ };
264
+ tooltip: {
265
+ trigger: string;
266
+ backgroundColor: string;
267
+ textStyle: {
268
+ color: string;
269
+ fontFamily: string;
270
+ fontSize: number;
271
+ };
272
+ borderColor: string;
273
+ borderWidth: number;
274
+ };
275
+ };
276
+ /** Line ou area chart. `step` produit la marche d'escalier des series de prix. */
277
+ export declare function rmLineOption(categories: (string | number)[], series: RmSeriesInput[], theme: RmChartTheme, palette: string[], options?: {
278
+ area?: boolean;
279
+ step?: boolean;
280
+ smooth?: boolean;
281
+ decal?: boolean;
282
+ }): {
283
+ legend: {
284
+ top: number;
285
+ textStyle: {
286
+ color: string;
287
+ fontSize: number;
288
+ fontFamily: string;
289
+ };
290
+ } | undefined;
291
+ xAxis: {
292
+ splitLine: {
293
+ show: boolean;
294
+ };
295
+ boundaryGap: boolean;
296
+ axisLabel: {
297
+ fontSize: number;
298
+ color: string;
299
+ fontFamily: string;
300
+ };
301
+ axisLine: {
302
+ lineStyle: {
303
+ color: string;
304
+ };
305
+ };
306
+ type: string;
307
+ data: (string | number)[];
308
+ };
309
+ yAxis: {
310
+ axisLabel: {
311
+ fontSize: number;
312
+ color: string;
313
+ fontFamily: string;
314
+ };
315
+ axisLine: {
316
+ lineStyle: {
317
+ color: string;
318
+ };
319
+ };
320
+ splitLine: {
321
+ lineStyle: {
322
+ color: string;
323
+ };
324
+ };
325
+ type: string;
326
+ };
327
+ series: {
328
+ type: string;
329
+ name: string | undefined;
330
+ data: (number | null)[];
331
+ smooth: boolean;
332
+ step: string | undefined;
333
+ symbol: string;
334
+ symbolSize: number;
335
+ lineStyle: {
336
+ color: string;
337
+ width: number;
338
+ };
339
+ itemStyle: {
340
+ color: string;
341
+ };
342
+ areaStyle: {
343
+ color: {
344
+ type: string;
345
+ x: number;
346
+ y: number;
347
+ x2: number;
348
+ y2: number;
349
+ colorStops: {
350
+ offset: number;
351
+ color: string;
352
+ }[];
353
+ };
354
+ } | undefined;
355
+ }[];
356
+ backgroundColor: string;
357
+ animation: boolean;
358
+ aria: {
359
+ enabled: boolean;
360
+ decal: {
361
+ show: boolean;
362
+ };
363
+ };
364
+ textStyle: {
365
+ fontFamily: string;
366
+ };
367
+ grid: {
368
+ left: number;
369
+ right: number;
370
+ top: number;
371
+ bottom: number;
372
+ containLabel: boolean;
373
+ };
374
+ tooltip: {
375
+ trigger: string;
376
+ backgroundColor: string;
377
+ textStyle: {
378
+ color: string;
379
+ fontFamily: string;
380
+ fontSize: number;
381
+ };
382
+ borderColor: string;
383
+ borderWidth: number;
384
+ };
385
+ };
386
+ /** Donut. Au-dela de 6 parts, regrouper le reste en "Autres". */
387
+ export declare function rmDonutOption(data: {
388
+ name: string;
389
+ value: number;
390
+ }[], theme: RmChartTheme, palette: string[], decal?: boolean): {
391
+ grid: undefined;
392
+ tooltip: {
393
+ trigger: string;
394
+ backgroundColor: string;
395
+ textStyle: {
396
+ color: string;
397
+ fontFamily: string;
398
+ fontSize: number;
399
+ };
400
+ borderColor: string;
401
+ borderWidth: number;
402
+ };
403
+ legend: {
404
+ bottom: number;
405
+ textStyle: {
406
+ color: string;
407
+ fontSize: number;
408
+ fontFamily: string;
409
+ };
410
+ };
411
+ series: {
412
+ type: string;
413
+ radius: string[];
414
+ center: string[];
415
+ data: {
416
+ value: number;
417
+ name: string;
418
+ itemStyle: {
419
+ color: string;
420
+ };
421
+ }[];
422
+ label: {
423
+ fontSize: number;
424
+ color: string;
425
+ fontFamily: string;
426
+ };
427
+ }[];
428
+ backgroundColor: string;
429
+ animation: boolean;
430
+ aria: {
431
+ enabled: boolean;
432
+ decal: {
433
+ show: boolean;
434
+ };
435
+ };
436
+ textStyle: {
437
+ fontFamily: string;
438
+ };
439
+ };
440
+ /** Sparkline : pas d'axes, pas de tooltip, destinee a une cellule ou une tuile KPI. */
441
+ export declare function rmSparklineOption(values: (number | null)[], color: string): {
442
+ backgroundColor: string;
443
+ animation: boolean;
444
+ grid: {
445
+ left: number;
446
+ right: number;
447
+ top: number;
448
+ bottom: number;
449
+ };
450
+ xAxis: {
451
+ type: string;
452
+ show: boolean;
453
+ data: number[];
454
+ };
455
+ yAxis: {
456
+ type: string;
457
+ show: boolean;
458
+ };
459
+ tooltip: {
460
+ show: boolean;
461
+ };
462
+ series: {
463
+ type: string;
464
+ data: (number | null)[];
465
+ smooth: boolean;
466
+ symbol: string;
467
+ lineStyle: {
468
+ color: string;
469
+ width: number;
470
+ };
471
+ areaStyle: {
472
+ color: string;
473
+ };
474
+ }[];
475
+ };
@@ -0,0 +1,209 @@
1
+ export const RM_PALETTES = {
2
+ default: {
3
+ cat: ["#2100AD", "#00D4A8", "#F59E0B", "#4164EE", "#EC4899", "#6B3FFF"],
4
+ primary: "#2100AD",
5
+ accent: "#6B3FFF",
6
+ neutral: "#D6CCFF",
7
+ seq: ["#EEF1FE", "#D6CCFF", "#B485FF", "#8B5CF6", "#6B3FFF", "#2100AD"]
8
+ },
9
+ defaultDark: {
10
+ cat: ["#9B7CFF", "#3DD9B9", "#FBBF24", "#7AA0FF", "#F472B6", "#B485FF"],
11
+ primary: "#9B7CFF",
12
+ accent: "#B485FF",
13
+ neutral: "#4A3F70",
14
+ seq: ["#1A1A24", "#2D2A42", "#4A3F70", "#7C5FE6", "#9B7CFF", "#D6CCFF"]
15
+ },
16
+ cvdSafe: {
17
+ cat: ["#0072B2", "#E69F00", "#009E73", "#CC79A7", "#56B4E9", "#F0E442"],
18
+ primary: "#0072B2",
19
+ accent: "#E69F00",
20
+ neutral: "#A3C9E2",
21
+ seq: ["#F0F7FB", "#D5E5F0", "#A3C9E2", "#56B4E9", "#0072B2", "#003E5C"]
22
+ },
23
+ cvdSafeDark: {
24
+ cat: ["#4DA8E5", "#FFB84D", "#3DD9A8", "#E5A0C8", "#7DD3F5", "#F5E76A"],
25
+ primary: "#4DA8E5",
26
+ accent: "#FFB84D",
27
+ neutral: "#3A4A5C",
28
+ seq: ["#1A1A24", "#2A3A4A", "#3A5A78", "#4DA8E5", "#7DD3F5", "#B3E5FC"]
29
+ }
30
+ };
31
+ export const RM_PRODUCT_PALETTES = {
32
+ generic: ["#2100AD", "#4164EE", "#7B9EF5", "#B8C8F8", "#DBDEF0"],
33
+ healthcare: ["#4164EE", "#1A2A8F", "#00D4A8", "#7B9EF5", "#B8C8F8"],
34
+ pharma: ["#F7520E", "#FF8C00", "#FFD600", "#FCA982", "#DBDEF0"],
35
+ discovery: ["#07816F", "#2ECC71", "#56CCF2", "#0DA38D", "#DBDEF0"]
36
+ };
37
+ export const RM_DIVERGING = ["#E53535", "#FCA5A5", "#F9F9FA", "#86EFAC", "#16A34A"];
38
+ const THEME_DAY = {
39
+ night: false,
40
+ textColor: "#1E293B",
41
+ mutedColor: "#525670",
42
+ dimmedColor: "#878CA6",
43
+ splitLine: "#F0F1F7",
44
+ axisLine: "#DBDEF0",
45
+ tooltipBg: "#1E293B",
46
+ tooltipText: "#FFFFFF",
47
+ tooltipBorder: "#344159",
48
+ backgroundColor: "transparent"
49
+ };
50
+ const THEME_NIGHT = {
51
+ night: true,
52
+ textColor: "#E5E7F0",
53
+ mutedColor: "#B8BCD0",
54
+ dimmedColor: "#7B8099",
55
+ splitLine: "#2F2F40",
56
+ axisLine: "#3A3A4E",
57
+ tooltipBg: "#232330",
58
+ tooltipText: "#E5E7F0",
59
+ tooltipBorder: "#3A3A4E",
60
+ backgroundColor: "transparent"
61
+ };
62
+ export const rmChartTheme = (night) => night ? THEME_NIGHT : THEME_DAY;
63
+ export function rmPalette(name = "default", night = false) {
64
+ const dark = `${name}Dark`;
65
+ if (night && RM_PALETTES[dark]) return RM_PALETTES[dark];
66
+ return RM_PALETTES[name] ?? RM_PALETTES.default;
67
+ }
68
+ export const RM_CHART_HEIGHTS = {
69
+ sparkline: 48,
70
+ kpi: 80,
71
+ compact: 200,
72
+ standard: 260,
73
+ large: 320,
74
+ full: 360
75
+ };
76
+ const FONT = "Figtree, system-ui, sans-serif";
77
+ export function rmBaseOption(theme, decal = false) {
78
+ return {
79
+ backgroundColor: theme.backgroundColor,
80
+ animation: true,
81
+ aria: { enabled: true, decal: { show: decal } },
82
+ textStyle: { fontFamily: FONT },
83
+ grid: { left: 48, right: 16, top: 24, bottom: 36, containLabel: true },
84
+ tooltip: {
85
+ trigger: "axis",
86
+ backgroundColor: theme.tooltipBg,
87
+ textStyle: { color: theme.tooltipText, fontFamily: FONT, fontSize: 12 },
88
+ borderColor: theme.tooltipBorder,
89
+ borderWidth: 1
90
+ }
91
+ };
92
+ }
93
+ const axisCommon = (theme) => ({
94
+ axisLabel: { fontSize: 11, color: theme.mutedColor, fontFamily: FONT },
95
+ axisLine: { lineStyle: { color: theme.axisLine } },
96
+ splitLine: { lineStyle: { color: theme.splitLine } }
97
+ });
98
+ export function rmBarVerticalOption(categories, series, theme, palette, decal = false) {
99
+ return {
100
+ ...rmBaseOption(theme, decal),
101
+ legend: series.length > 1 ? { top: 0, textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT } } : void 0,
102
+ xAxis: { type: "category", data: categories, ...axisCommon(theme), splitLine: { show: false } },
103
+ yAxis: { type: "value", ...axisCommon(theme) },
104
+ series: series.map((s, i) => ({
105
+ type: "bar",
106
+ name: s.name,
107
+ stack: s.stack,
108
+ data: s.data,
109
+ itemStyle: { color: s.color ?? palette[i % palette.length], borderRadius: [3, 3, 0, 0] }
110
+ }))
111
+ };
112
+ }
113
+ export function rmBarHorizontalOption(labels, values, theme, palette, decal = false, formatter) {
114
+ return {
115
+ ...rmBaseOption(theme, decal),
116
+ grid: { left: 140, right: 60, top: 16, bottom: 24, containLabel: true },
117
+ xAxis: { type: "value", ...axisCommon(theme) },
118
+ yAxis: { type: "category", data: labels, ...axisCommon(theme), splitLine: { show: false } },
119
+ series: [{
120
+ type: "bar",
121
+ data: values,
122
+ itemStyle: {
123
+ color: (params) => palette[params.dataIndex % palette.length],
124
+ borderRadius: [0, 3, 3, 0]
125
+ },
126
+ label: {
127
+ show: true,
128
+ position: "right",
129
+ fontSize: 11,
130
+ color: theme.mutedColor,
131
+ fontFamily: FONT,
132
+ formatter: formatter ? (p) => formatter(p.value) : void 0
133
+ }
134
+ }]
135
+ };
136
+ }
137
+ export function rmLineOption(categories, series, theme, palette, options = {}) {
138
+ const { area = false, step = false, smooth = true, decal = false } = options;
139
+ return {
140
+ ...rmBaseOption(theme, decal),
141
+ legend: series.length > 1 ? { top: 0, textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT } } : void 0,
142
+ xAxis: { type: "category", data: categories, ...axisCommon(theme), splitLine: { show: false }, boundaryGap: false },
143
+ yAxis: { type: "value", ...axisCommon(theme) },
144
+ series: series.map((s, i) => {
145
+ const color = s.color ?? palette[i % palette.length];
146
+ return {
147
+ type: "line",
148
+ name: s.name,
149
+ data: s.data,
150
+ smooth: step ? false : smooth,
151
+ step: step ? "end" : void 0,
152
+ symbol: "circle",
153
+ symbolSize: 5,
154
+ lineStyle: { color, width: 2.5 },
155
+ itemStyle: { color },
156
+ areaStyle: area ? {
157
+ color: {
158
+ type: "linear",
159
+ x: 0,
160
+ y: 0,
161
+ x2: 0,
162
+ y2: 1,
163
+ colorStops: [
164
+ { offset: 0, color: `${color}30` },
165
+ { offset: 1, color: `${color}00` }
166
+ ]
167
+ }
168
+ } : void 0
169
+ };
170
+ })
171
+ };
172
+ }
173
+ export function rmDonutOption(data, theme, palette, decal = false) {
174
+ return {
175
+ ...rmBaseOption(theme, decal),
176
+ grid: void 0,
177
+ tooltip: { ...rmBaseOption(theme, decal).tooltip, trigger: "item" },
178
+ legend: { bottom: 0, textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT } },
179
+ series: [{
180
+ type: "pie",
181
+ radius: ["45%", "70%"],
182
+ center: ["50%", "46%"],
183
+ data: data.map((d, i) => ({
184
+ value: d.value,
185
+ name: d.name,
186
+ itemStyle: { color: palette[i % palette.length] }
187
+ })),
188
+ label: { fontSize: 11, color: theme.mutedColor, fontFamily: FONT }
189
+ }]
190
+ };
191
+ }
192
+ export function rmSparklineOption(values, color) {
193
+ return {
194
+ backgroundColor: "transparent",
195
+ animation: true,
196
+ grid: { left: 0, right: 0, top: 2, bottom: 2 },
197
+ xAxis: { type: "category", show: false, data: values.map((_, i) => i) },
198
+ yAxis: { type: "value", show: false },
199
+ tooltip: { show: false },
200
+ series: [{
201
+ type: "line",
202
+ data: values,
203
+ smooth: true,
204
+ symbol: "none",
205
+ lineStyle: { color, width: 1.5 },
206
+ areaStyle: { color: `${color}20` }
207
+ }]
208
+ };
209
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,7 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@nuxt/kit": "^3.16.0",
30
- "@phosphor-icons/vue": "^2.2.1"
30
+ "@phosphor-icons/vue": "^2.2.1",
31
+ "echarts": "^5.6.0"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@nuxt/module-builder": "^0.8.4",