@ons/design-system 72.5.0 → 72.6.0

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 (37) hide show
  1. package/components/chart/_chart.scss +51 -0
  2. package/components/chart/_macro.njk +27 -3
  3. package/components/chart/_macro.spec.js +388 -0
  4. package/components/chart/annotations-options.js +78 -0
  5. package/components/chart/bar-chart.js +6 -2
  6. package/components/chart/chart.js +111 -26
  7. package/components/chart/column-chart.js +2 -2
  8. package/components/chart/common-chart-options.js +97 -50
  9. package/components/chart/example-bar-chart-with-annotations.njk +62 -0
  10. package/components/chart/example-bar-chart.njk +1 -0
  11. package/components/chart/example-bar-with-line-chart.njk +64 -0
  12. package/components/chart/example-clustered-column-chart.njk +3 -1
  13. package/components/chart/example-column-chart-with-annotations.njk +62 -0
  14. package/components/chart/example-column-chart.njk +3 -1
  15. package/components/chart/example-column-with-line-chart.njk +62 -0
  16. package/components/chart/example-line-chart-with-annotations.njk +235 -0
  17. package/components/chart/example-line-chart.njk +4 -2
  18. package/components/chart/example-stacked-column-chart.njk +3 -1
  19. package/components/chart/line-chart.js +2 -7
  20. package/components/hero/_hero.scss +31 -0
  21. package/components/hero/_macro.njk +20 -9
  22. package/components/hero/_macro.spec.js +94 -0
  23. package/components/hero/example-hero-grey.njk +8 -0
  24. package/components/icon/_macro.njk +1 -1
  25. package/components/pagination/_pagination.scss +7 -1
  26. package/components/summary/_macro.njk +1 -1
  27. package/components/summary/_macro.spec.js +6 -0
  28. package/components/table-of-contents/_macro.njk +40 -0
  29. package/components/table-of-contents/_macro.spec.js +72 -0
  30. package/components/table-of-contents/_table-of-contents.scss +11 -0
  31. package/components/table-of-contents/example-table-of-contents-related-links-with-button.njk +60 -0
  32. package/css/main.css +1 -1
  33. package/js/cookies-functions.js +11 -6
  34. package/js/cookies-functions.spec.js +44 -0
  35. package/package.json +1 -1
  36. package/scripts/main.es5.js +1 -1
  37. package/scripts/main.js +1 -1
@@ -1,5 +1,7 @@
1
1
  .ons-chart {
2
2
  margin: 0;
3
+ container-type: inline-size;
4
+ max-width: $grid-max-width;
3
5
 
4
6
  &__download-title {
5
7
  @extend .ons-u-pt-l;
@@ -21,6 +23,55 @@
21
23
  line-height: 1.2rem;
22
24
  color: var(--ons-color-grey-75);
23
25
  }
26
+
27
+ &__footnote-number {
28
+ display: flex;
29
+ flex-shrink: 0;
30
+ flex-grow: 0;
31
+ align-items: center;
32
+ justify-content: center;
33
+ min-width: 1.125rem; // 18px
34
+ min-height: 1.125rem; // 18px
35
+ padding: 0.2143em; // 3px;
36
+ aspect-ratio: 1;
37
+ border: 1px solid var(--ons-color-grey-100);
38
+ border-radius: 50%;
39
+ font-size: 0.875rem; // 14px
40
+ line-height: 0;
41
+ background-color: var(--ons-color-white);
42
+ color: var(--ons-color-grey-100);
43
+ }
44
+
45
+ /* Override Highcharts styling */
46
+ &__footnote-number span {
47
+ position: static !important;
48
+ transform-origin: 0 0 !important;
49
+ font-size: inherit !important;
50
+ color: inherit !important;
51
+ top: 0 !important;
52
+ left: 0 !important;
53
+ }
54
+
55
+ &__footnotes {
56
+ list-style: none;
57
+ padding: 0;
58
+ margin: 0 0 1rem;
59
+
60
+ // We need to use a container query here in order to match the responsive rules for Highcharts
61
+ // which is based on the width that the chart renders not the viewport
62
+ // Update the breakpoint value here if the Highcharts responsive rules change
63
+ @container (min-width: 601px) {
64
+ display: none;
65
+ }
66
+ }
67
+
68
+ &__footnote_item {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 0.5rem;
72
+ font-size: 0.875rem; // 14px
73
+ color: var(--ons-color-grey-100);
74
+ }
24
75
  }
25
76
 
26
77
  // This is a workaround to position the axis title to the left
@@ -23,6 +23,12 @@
23
23
  data-highcharts-id="{{ params.id }}"
24
24
  {% if params.useStackedLayout %}data-highcharts-use-stacked-layout="{{ params.useStackedLayout }}"{% endif %}
25
25
  id="{{ params.id }}"
26
+ {% if params.percentageHeightDesktop and params.chartType != 'bar' %}
27
+ data-highcharts-percentage-height-desktop="{{ params.percentageHeightDesktop }}"
28
+ {% endif %}
29
+ {% if params.percentageHeightMobile and params.chartType != 'bar' %}
30
+ data-highcharts-percentage-height-mobile="{{ params.percentageHeightMobile }}"
31
+ {% endif %}
26
32
  >
27
33
  {% if params.chartType in supportedChartTypes %}
28
34
  <figure class="ons-chart">
@@ -33,6 +39,18 @@
33
39
  <p class="ons-u-vh">{{ params.description }}</p>
34
40
  {% endif %}
35
41
  <div data-highcharts-chart></div>
42
+ {#
43
+ Footnotes for the annotations at mobile
44
+ Hidden from screen readers because the full text will be read out where they appear in the chart
45
+ #}
46
+ <ul class="ons-chart__footnotes" aria-hidden="true">
47
+ {% for annotation in params.annotations %}
48
+ <li class="ons-chart__footnote_item">
49
+ <span class="ons-chart__footnote-number">{{ loop.index }}</span>
50
+ {{ annotation.text }}
51
+ </li>
52
+ {% endfor %}
53
+ </ul>
36
54
  {% if params.caption %}
37
55
  <figcaption class="ons-chart__caption">{{ params.caption }}</figcaption>
38
56
  {% endif %}
@@ -60,14 +78,15 @@
60
78
  },
61
79
  "dataLabels": {
62
80
  "enabled": item.dataLabels if item.dataLabels else false
63
- }
81
+ },
82
+ "type": item.type if item.type and item.type == 'line' else params.chartType
64
83
  })
65
84
  %}
66
85
  {% endfor %}
67
86
  {%
68
87
  set config = {
69
88
  "legend": {
70
- "enabled" : params.legend
89
+ "enabled" : params.legend if params.legend else true
71
90
  },
72
91
  "yAxis": {
73
92
  "title": {
@@ -94,8 +113,13 @@
94
113
  <!-- Set scripts to pass the config values as json to the javascript -->
95
114
  <!-- prettier-ignore-start -->
96
115
  <script type="application/json" data-highcharts-config--{{ params.id }}>
97
- {{ config | dump | safe }}
116
+ {{ config | tojson }}
98
117
  </script>
118
+ {% if params.annotations %}
119
+ <script type="application/json" data-highcharts-annotations--{{ params.id }}>
120
+ {{ params.annotations | tojson }}
121
+ </script>
122
+ {% endif %}
99
123
  <!-- prettier-ignore-end -->
100
124
  </div>
101
125
  {% endmacro %}
@@ -8,7 +8,13 @@ import {
8
8
  EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
9
9
  EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS,
10
10
  EXAMPLE_BAR_CHART_PARAMS,
11
+ EXAMPLE_BAR_CHART_WITH_PERCENTAGE_HEIGHT_PARAMS,
11
12
  EXAMPLE_COLUMN_CHART_PARAMS,
13
+ EXAMPLE_LINE_CHART_WITH_ANNOTATIONS_PARAMS,
14
+ EXAMPLE_BAR_CHART_WITH_ANNOTATIONS_PARAMS,
15
+ EXAMPLE_COLUMN_CHART_WITH_ANNOTATIONS_PARAMS,
16
+ EXAMPLE_BAR_WITH_LINE_CHART_PARAMS,
17
+ EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS,
12
18
  } from './_test-examples';
13
19
 
14
20
  describe('Macro: Chart', () => {
@@ -119,6 +125,24 @@ describe('Macro: Chart', () => {
119
125
  });
120
126
  });
121
127
 
128
+ describe('GIVEN: Params: Percentage Height Desktop', () => {
129
+ describe('WHEN: percentage height desktop is provided', () => {
130
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS));
131
+ test('THEN: it includes correct percentage height desktop', () => {
132
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-percentage-height-desktop')).toBe('50');
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('GIVEN: Params: Percentage Height Mobile', () => {
138
+ describe('WHEN: percentage height mobile is provided', () => {
139
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS));
140
+ test('THEN: it includes correct percentage height mobile', () => {
141
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-percentage-height-mobile')).toBe('120');
142
+ });
143
+ });
144
+ });
145
+
122
146
  describe('GIVEN: Params: Caption', () => {
123
147
  describe('WHEN: caption is provided', () => {
124
148
  const $ = cheerio.load(
@@ -229,6 +253,24 @@ describe('Macro: Chart', () => {
229
253
  });
230
254
  });
231
255
 
256
+ describe('GIVEN: Params: Percentage Height Desktop', () => {
257
+ describe('WHEN: percentage height desktop is provided', () => {
258
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_BAR_CHART_WITH_PERCENTAGE_HEIGHT_PARAMS));
259
+ test('THEN: it does not include percentage height desktop', () => {
260
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-percentage-height-desktop')).toBe(undefined);
261
+ });
262
+ });
263
+ });
264
+
265
+ describe('GIVEN: Params: Percentage Height Mobile', () => {
266
+ describe('WHEN: percentage height mobile is provided', () => {
267
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_BAR_CHART_WITH_PERCENTAGE_HEIGHT_PARAMS));
268
+ test('THEN: it does not include percentage height mobile', () => {
269
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-percentage-height-mobile')).toBe(undefined);
270
+ });
271
+ });
272
+ });
273
+
232
274
  describe('GIVEN: Params: Caption', () => {
233
275
  describe('WHEN: caption is provided', () => {
234
276
  const $ = cheerio.load(
@@ -358,6 +400,24 @@ describe('Macro: Chart', () => {
358
400
  });
359
401
  });
360
402
 
403
+ describe('GIVEN: Params: Percentage Height Desktop', () => {
404
+ describe('WHEN: percentage height desktop is provided', () => {
405
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_COLUMN_CHART_PARAMS));
406
+ test('THEN: it includes correct percentage height desktop', () => {
407
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-percentage-height-desktop')).toBe('50');
408
+ });
409
+ });
410
+ });
411
+
412
+ describe('GIVEN: Params: Percentage Height Mobile', () => {
413
+ describe('WHEN: percentage height mobile is provided', () => {
414
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_COLUMN_CHART_PARAMS));
415
+ test('THEN: it includes correct percentage height mobile', () => {
416
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-percentage-height-mobile')).toBe('120');
417
+ });
418
+ });
419
+ });
420
+
361
421
  describe('GIVEN: Params: Caption', () => {
362
422
  describe('WHEN: caption is provided', () => {
363
423
  const $ = cheerio.load(
@@ -434,4 +494,332 @@ describe('Macro: Chart', () => {
434
494
  });
435
495
  });
436
496
  });
497
+
498
+ describe('FOR: Line chart with annotations', () => {
499
+ describe('GIVEN: Params: Annotations', () => {
500
+ describe('WHEN: annotations params are provided', () => {
501
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_LINE_CHART_WITH_ANNOTATIONS_PARAMS));
502
+
503
+ test('THEN: it passes jest-axe checks', async () => {
504
+ const results = await axe($.html());
505
+ expect(results).toHaveNoViolations();
506
+ });
507
+
508
+ test('THEN: it renders the footnotes', () => {
509
+ expect($('.ons-chart__footnotes').text()).toContain('1');
510
+ expect($('.ons-chart__footnotes').text()).toContain('A test annotation');
511
+ expect($('.ons-chart__footnotes').text()).toContain('2');
512
+ expect($('.ons-chart__footnotes').text()).toContain('Another test annotation');
513
+ });
514
+
515
+ test('THEN: the footnotes are hidden from screen readers', () => {
516
+ expect($('.ons-chart__footnotes').attr('aria-hidden')).toBe('true');
517
+ });
518
+
519
+ test('THEN: it includes the Annotations JSON config', () => {
520
+ const configScript = $(`script[data-highcharts-annotations--line-chart-annotations-123]`).html();
521
+ expect(configScript).toContain('"text":"A test annotation"');
522
+ expect(configScript).toContain('"point":{"x":10,"y":1.3}');
523
+ expect(configScript).toContain('"labelOffsetX":10,"labelOffsetY":-50');
524
+ });
525
+ });
526
+ });
527
+ });
528
+
529
+ describe('FOR: Bar chart with annotations', () => {
530
+ describe('GIVEN: Params: Annotations', () => {
531
+ describe('WHEN: annotations params are provided', () => {
532
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_BAR_CHART_WITH_ANNOTATIONS_PARAMS));
533
+ test('THEN: it passes jest-axe checks', async () => {
534
+ const results = await axe($.html());
535
+ expect(results).toHaveNoViolations();
536
+ });
537
+
538
+ test('THEN: it renders the footnotes', () => {
539
+ expect($('.ons-chart__footnotes').text()).toContain('1');
540
+ expect($('.ons-chart__footnotes').text()).toContain('A test annotation');
541
+ });
542
+
543
+ test('THEN: the footnotes are hidden from screen readers', () => {
544
+ expect($('.ons-chart__footnotes').attr('aria-hidden')).toBe('true');
545
+ });
546
+
547
+ test('THEN: it includes the Annotations JSON config', () => {
548
+ const configScript = $(`script[data-highcharts-annotations--bar-chart-annotations-123]`).html();
549
+ expect(configScript).toContain('"text":"A test annotation"');
550
+ expect(configScript).toContain('"point":{"x":2,"y":3}');
551
+ expect(configScript).toContain('"labelOffsetX":10,"labelOffsetY":-50');
552
+ });
553
+ });
554
+ });
555
+ });
556
+
557
+ describe('FOR: Column chart with annotations', () => {
558
+ describe('GIVEN: Params: Annotations', () => {
559
+ describe('WHEN: annotations params are provided', () => {
560
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_COLUMN_CHART_WITH_ANNOTATIONS_PARAMS));
561
+
562
+ test('THEN: it passes jest-axe checks', async () => {
563
+ const results = await axe($.html());
564
+ expect(results).toHaveNoViolations();
565
+ });
566
+
567
+ test('THEN: it renders the footnotes', () => {
568
+ expect($('.ons-chart__footnotes').text()).toContain('1');
569
+ expect($('.ons-chart__footnotes').text()).toContain('A test annotation');
570
+ });
571
+
572
+ test('THEN: the footnotes are hidden from screen readers', () => {
573
+ expect($('.ons-chart__footnotes').attr('aria-hidden')).toBe('true');
574
+ });
575
+
576
+ test('THEN: it includes the Annotations JSON config', () => {
577
+ const configScript = $(`script[data-highcharts-annotations--column-chart-annotations-123]`).html();
578
+ expect(configScript).toContain('"text":"A test annotation"');
579
+ expect(configScript).toContain('"point":{"x":11,"y":31.8}');
580
+ expect(configScript).toContain('"labelOffsetX":10,"labelOffsetY":-50');
581
+ });
582
+ });
583
+ });
584
+ });
585
+
586
+ describe('FOR: Bar Chart with Line', () => {
587
+ describe('GIVEN: Params: required', () => {
588
+ describe('WHEN: required params are provided', () => {
589
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_BAR_WITH_LINE_CHART_PARAMS));
590
+ const configScript = $(`script[data-highcharts-config--bar-chart-123]`).html();
591
+
592
+ test('THEN: it passes jest-axe checks', async () => {
593
+ const results = await axe($.html());
594
+ expect(results).toHaveNoViolations();
595
+ });
596
+
597
+ test('THEN: it includes one series of type "bar" and another of type "line"', () => {
598
+ expect(configScript).toContain('"type":"bar"');
599
+ expect(configScript).toContain('"type":"line"');
600
+ });
601
+
602
+ test('THEN: it renders the chart container with correct data attributes', () => {
603
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('bar');
604
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-theme')).toBe('alternate');
605
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-title')).toBe('Example Bar Chart');
606
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-id')).toBe('bar-chart-123');
607
+ });
608
+
609
+ test('THEN: it includes the Highcharts JSON config', () => {
610
+ expect(configScript).toContain('"text":"X Axis Title"');
611
+ expect(configScript).toContain('"text":"Y Axis Title"');
612
+ });
613
+ });
614
+ });
615
+
616
+ describe('GIVEN: Params: Legend', () => {
617
+ describe('WHEN: legend is enabled', () => {
618
+ const $ = cheerio.load(renderComponent('chart', { ...EXAMPLE_BAR_WITH_LINE_CHART_PARAMS, legend: false }));
619
+
620
+ test('THEN: it renders the legend', () => {
621
+ const configScript = $(`script[data-highcharts-config--bar-chart-123]`).html();
622
+ expect(configScript).toContain('"enabled":false');
623
+ });
624
+ });
625
+ });
626
+
627
+ describe('GIVEN: Params: Caption', () => {
628
+ describe('WHEN: caption is provided', () => {
629
+ const $ = cheerio.load(
630
+ renderComponent('chart', {
631
+ ...EXAMPLE_BAR_WITH_LINE_CHART_PARAMS,
632
+ caption: 'This is an example caption for the chart.',
633
+ }),
634
+ );
635
+
636
+ test('THEN: it renders the caption when provided', () => {
637
+ expect($('figcaption').text()).toBe('This is an example caption for the chart.');
638
+ });
639
+ });
640
+ });
641
+
642
+ describe('GIVEN: Params: Description', () => {
643
+ describe('WHEN: description is provided', () => {
644
+ const $ = cheerio.load(
645
+ renderComponent('chart', {
646
+ ...EXAMPLE_BAR_WITH_LINE_CHART_PARAMS,
647
+ description: 'An accessible description for screen readers.',
648
+ }),
649
+ );
650
+
651
+ test('THEN: it renders the description for accessibility', () => {
652
+ expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
653
+ });
654
+ });
655
+ });
656
+
657
+ describe('GIVEN: Params: Series: Type', () => {
658
+ describe('WHEN: a series has an invalid type', () => {
659
+ const invalidTypeParams = {
660
+ ...EXAMPLE_BAR_WITH_LINE_CHART_PARAMS,
661
+ series: [
662
+ { name: 'Invalid Series', data: [5, 15, 25], type: 'scatter' },
663
+ { name: 'Valid Line Series', data: [10, 20, 30], type: 'line' },
664
+ ],
665
+ };
666
+
667
+ const $ = cheerio.load(renderComponent('chart', invalidTypeParams));
668
+ const configScript = $(`script[data-highcharts-config--bar-chart-123]`).html();
669
+
670
+ test('THEN: it defaults non-line series type to the chartType', () => {
671
+ expect(configScript).not.toContain('"type":"scatter"');
672
+ expect(configScript).toContain('"type":"bar"');
673
+ expect(configScript).toContain('"type":"line"');
674
+ });
675
+ });
676
+ });
677
+
678
+ describe('GIVEN: Params: Download', () => {
679
+ describe('WHEN: download object are provided', () => {
680
+ const $ = cheerio.load(
681
+ renderComponent('chart', {
682
+ ...EXAMPLE_BAR_WITH_LINE_CHART_PARAMS,
683
+ download: {
684
+ title: 'Download Chart Data',
685
+ itemsList: [
686
+ { text: 'Download as PNG', url: 'https://example.com/chart.png' },
687
+ { text: 'Download as CSV', url: 'https://example.com/chart.csv' },
688
+ ],
689
+ },
690
+ }),
691
+ );
692
+
693
+ test('THEN: it renders the download section correctly', () => {
694
+ expect($('.ons-chart__download-title').text()).toBe('Download Chart Data');
695
+
696
+ const downloadLinks = $('.ons-chart__download-title').next().find('li a');
697
+ expect(downloadLinks.eq(0).text()).toBe('Download as PNG');
698
+ expect(downloadLinks.eq(0).attr('href')).toBe('https://example.com/chart.png');
699
+ expect(downloadLinks.eq(1).text()).toBe('Download as CSV');
700
+ expect(downloadLinks.eq(1).attr('href')).toBe('https://example.com/chart.csv');
701
+ });
702
+ });
703
+ });
704
+ });
705
+
706
+ describe('FOR: Column Chart with Line', () => {
707
+ describe('GIVEN: Params: required', () => {
708
+ describe('WHEN: required params are provided', () => {
709
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS));
710
+ const configScript = $(`script[data-highcharts-config--column-chart-123]`).html();
711
+
712
+ test('THEN: it passes jest-axe checks', async () => {
713
+ const results = await axe($.html());
714
+ expect(results).toHaveNoViolations();
715
+ });
716
+
717
+ test('THEN: it includes one series of type "column" and another of type "line"', () => {
718
+ expect(configScript).toContain('"type":"column"');
719
+ expect(configScript).toContain('"type":"line"');
720
+ });
721
+
722
+ test('THEN: it renders the chart container with correct data attributes', () => {
723
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('column');
724
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-theme')).toBe('alternate');
725
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-title')).toBe('Example Column Chart');
726
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-id')).toBe('column-chart-123');
727
+ });
728
+
729
+ test('THEN: it includes the Highcharts JSON config', () => {
730
+ expect(configScript).toContain('"text":"X Axis Title"');
731
+ expect(configScript).toContain('"text":"Y Axis Title"');
732
+ });
733
+ });
734
+ });
735
+
736
+ describe('GIVEN: Params: Legend', () => {
737
+ describe('WHEN: legend is enabled', () => {
738
+ const $ = cheerio.load(renderComponent('chart', { ...EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS, legend: false }));
739
+
740
+ test('THEN: it renders the legend', () => {
741
+ const configScript = $(`script[data-highcharts-config--column-chart-123]`).html();
742
+ expect(configScript).toContain('"enabled":false');
743
+ });
744
+ });
745
+ });
746
+
747
+ describe('GIVEN: Params: Caption', () => {
748
+ describe('WHEN: caption is provided', () => {
749
+ const $ = cheerio.load(
750
+ renderComponent('chart', {
751
+ ...EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS,
752
+ caption: 'This is an example caption for the chart.',
753
+ }),
754
+ );
755
+
756
+ test('THEN: it renders the caption when provided', () => {
757
+ expect($('figcaption').text()).toBe('This is an example caption for the chart.');
758
+ });
759
+ });
760
+ });
761
+
762
+ describe('GIVEN: Params: Description', () => {
763
+ describe('WHEN: description is provided', () => {
764
+ const $ = cheerio.load(
765
+ renderComponent('chart', {
766
+ ...EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS,
767
+ description: 'An accessible description for screen readers.',
768
+ }),
769
+ );
770
+
771
+ test('THEN: it renders the description for accessibility', () => {
772
+ expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
773
+ });
774
+ });
775
+ });
776
+
777
+ describe('GIVEN: Params: Download', () => {
778
+ describe('WHEN: download object is provided', () => {
779
+ const $ = cheerio.load(
780
+ renderComponent('chart', {
781
+ ...EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS,
782
+ download: {
783
+ title: 'Download Chart Data',
784
+ itemsList: [
785
+ { text: 'Download as PNG', url: 'https://example.com/chart.png' },
786
+ { text: 'Download as CSV', url: 'https://example.com/chart.csv' },
787
+ ],
788
+ },
789
+ }),
790
+ );
791
+
792
+ test('THEN: it renders the download section correctly', () => {
793
+ expect($('.ons-chart__download-title').text()).toBe('Download Chart Data');
794
+
795
+ const downloadLinks = $('.ons-chart__download-title').next().find('li a');
796
+ expect(downloadLinks.eq(0).text()).toBe('Download as PNG');
797
+ expect(downloadLinks.eq(0).attr('href')).toBe('https://example.com/chart.png');
798
+ expect(downloadLinks.eq(1).text()).toBe('Download as CSV');
799
+ expect(downloadLinks.eq(1).attr('href')).toBe('https://example.com/chart.csv');
800
+ });
801
+ });
802
+ });
803
+
804
+ describe('GIVEN: Params: Series: Type', () => {
805
+ describe('WHEN: a series has an invalid type', () => {
806
+ const invalidTypeParams = {
807
+ ...EXAMPLE_COLUMN_WITH_LINE_CHART_PARAMS,
808
+ series: [
809
+ { name: 'Invalid Series', data: [5, 15, 25], type: 'scatter' },
810
+ { name: 'Valid Line Series', data: [10, 20, 30], type: 'line' },
811
+ ],
812
+ };
813
+
814
+ const $ = cheerio.load(renderComponent('chart', invalidTypeParams));
815
+ const configScript = $(`script[data-highcharts-config--column-chart-123]`).html();
816
+
817
+ test('THEN: it defaults non-line series type to the chartType', () => {
818
+ expect(configScript).not.toContain('"type":"scatter"');
819
+ expect(configScript).toContain('"type":"column"');
820
+ expect(configScript).toContain('"type":"line"');
821
+ });
822
+ });
823
+ });
824
+ });
437
825
  });
@@ -0,0 +1,78 @@
1
+ import ChartConstants from './chart-constants';
2
+
3
+ class AnnotationsOptions {
4
+ constructor(annotations) {
5
+ this.constants = ChartConstants.constants();
6
+ this.annotations = annotations;
7
+ }
8
+
9
+ getAnnotationsOptionsDesktop = () => {
10
+ let annotations = [
11
+ {
12
+ labels: [],
13
+ labelOptions: {
14
+ shape: 'connector',
15
+ borderColor: this.constants.labelColor,
16
+ padding: 3,
17
+ style: {
18
+ color: this.constants.labelColor,
19
+ fontSize: this.constants.desktopFontSize,
20
+ width: 150,
21
+ textAlign: 'left',
22
+ },
23
+ },
24
+ draggable: '',
25
+ },
26
+ ];
27
+ this.annotations.forEach((annotation) => {
28
+ annotations[0].labels.push({
29
+ point: {
30
+ x: annotation.point.x,
31
+ y: annotation.point.y,
32
+ xAxis: 0,
33
+ yAxis: 0,
34
+ },
35
+ text: annotation.text,
36
+ x: annotation.labelOffsetX,
37
+ y: annotation.labelOffsetY,
38
+ });
39
+ });
40
+ return annotations;
41
+ };
42
+
43
+ getAnnotationsOptionsMobile = () => {
44
+ let annotations = [
45
+ {
46
+ labels: [],
47
+ labelOptions: {
48
+ backgroundColor: 'transparent',
49
+ borderColor: 'transparent',
50
+ // We use css styling for the rounded number annotation at mobile
51
+ useHTML: true,
52
+ className: 'ons-chart__footnote-number',
53
+ },
54
+ draggable: '',
55
+ },
56
+ ];
57
+ this.annotations.forEach((annotation, index) => {
58
+ annotations[0].labels.push({
59
+ point: {
60
+ x: annotation.point.x,
61
+ y: annotation.point.y,
62
+ xAxis: 0,
63
+ yAxis: 0,
64
+ },
65
+ text: index + 1,
66
+ x: 0,
67
+ y: 0,
68
+ // Ensures the full label is read out by screen readers
69
+ accessibility: {
70
+ description: annotation.text,
71
+ },
72
+ });
73
+ });
74
+ return annotations;
75
+ };
76
+ }
77
+
78
+ export default AnnotationsOptions;
@@ -76,6 +76,10 @@ class BarChart {
76
76
  // Get the actual width of the data label
77
77
  const labelWidth = point.dataLabel && point.dataLabel.getBBox().width;
78
78
  // Move the data labels inside the bar if the bar is wider than the label plus some padding
79
+ if (series.type == 'line') {
80
+ // If we have a bar chart with an extra line, exit early for the line series
81
+ return;
82
+ }
79
83
  if (point.shapeArgs.height > labelWidth + 20) {
80
84
  point.update(insideOptions, false);
81
85
  } else {
@@ -112,9 +116,9 @@ class BarChart {
112
116
 
113
117
  // This updates the height of the vertical axis and overall chart to fit the number of categories
114
118
  // Note that the vertical axis on a bar chart is the x axis
115
- updateBarChartHeight = (config, currentChart, useStackedLayout) => {
119
+ updateBarChartHeight = (config, currentChart, useStackedLayout, numberOfExtraLines) => {
116
120
  const numberOfCategories = config.xAxis.categories.length;
117
- const numberOfSeries = currentChart.series.length; // Get number of bar series
121
+ const numberOfSeries = currentChart.series.length - numberOfExtraLines; // Get number of bar series
118
122
  let barHeight = 30; // Height of each individual bar - set in bar-chart-plot-options
119
123
  let groupSpacing = 0; // Space we want between category groups, or between series groups for cluster charts
120
124
  let categoriesTotalHeight = 0;