UNPKG

8.43 kBPlain TextView Raw
1<template>
2 <div class="vue-charts-css" :style="style">
3 <table
4 :class="chartClasses"
5 >
6 <caption v-if="heading !== null || $slots.heading" class="heading">
7 <slot name="heading" :heading="heading">{{ heading }}</slot>
8 </caption>
9
10 <tbody>
11 <tr
12 v-for="(row, rowIndex) in rows"
13 :key="rowIndex"
14 >
15 <th scope="row"><slot name="label" :label="labels[rowIndex]" :labelIndex="rowIndex">{{ labels[rowIndex] }}</slot></th>
16
17 <td
18 v-for="(value, colIndex) in row"
19 :key="colIndex"
20 :style="resolveDataStyle(value, rowIndex, colIndex)"
21 >
22 <span class="data">
23 <slot name="data" :value="value" :formattedValue="formatDataValue(value.valueRaw)">
24 {{ formatDataValue(value.valueRaw) }}
25 </slot>
26 </span>
27 <span v-if="value.tooltip" class="tooltip">{{ value.tooltip }}</span>
28 </td>
29 </tr>
30 </tbody>
31 </table>
32
33 <slot
34 name="legend"
35 v-if="( $slots.legend || showLegend ) && this.datasets.length > 0"
36 :datasets="datasets"
37 >
38 <ul :class="legendClasses">
39 <li v-for="(dataset, index) in datasets" :key="index + '' + datasets.length">
40 {{ dataset.name }}
41 </li>
42 </ul>
43 </slot>
44 </div>
45</template>
46
47<script>
48 export default {
49 name: "charts-css",
50
51 props: {
52 type: { type: String, required: true, },
53 heading: { type: String },
54 headingSize: { type: String, default: "1rem", },
55 showHeading: { type: Boolean, default: false, },
56
57 labels: { type: Array, required: true, },
58 showLabels: { type: Boolean, default: false, },
59
60 dataSpacing: { type: Number, default: 0, },
61 hideData: { type: Boolean, default: false, },
62 showDataAxis: { type: Boolean, default: false, },
63 showDataOnHover: { type: Boolean, default: false, },
64
65 datasets: { type: Array, required: true, },
66
67 showLegend: { type: Boolean, default: false, },
68 legendInline: { type: Boolean, default: true, },
69 legendType: { type: String, default: "square", },
70
71 showTooltips: { type: Boolean, default: false, },
72 resolveDataTooltip: {
73 type: Function,
74 default: (value, label, datasetName, rowIndex, colIndex, hasMultipleDatasets = false) => {
75 return ( datasetName && hasMultipleDatasets ? datasetName : label ) + ": " + value;
76 },
77 },
78
79 reverse: { type: Boolean, default: false, },
80 stacked: { type: Boolean, default: false, },
81
82 classes: { type: String, },
83
84 color: { type: String, default: null, },
85
86 formatDataValue: { type: Function, default: (value) => value },
87 resolveDataColor: {
88 type: Function,
89 default: (value, label, datasetName, rowIndex, colIndex, hasMultipleDatasets = false) => null,
90 },
91 },
92
93 computed: {
94 style()
95 {
96 let style = `--heading-size: ${this.headingSize};`
97
98 if (this.color){
99 style += `--color: ${this.color};`;
100 }
101
102 return style;
103 },
104
105 legendClasses(){
106 if (this.showLegend){
107 return "charts-css legend " + ( this.legendInline ? 'legend-inline':'' ) + " legend-" + this.legendType;
108 }
109 return "";
110 },
111
112 chartClasses()
113 {
114 let propClasses = {
115 "multiple": this.datasets.length > 1,
116 "reverse": this.reverse,
117 "show-heading": this.showHeading,
118 "hide-data": this.hideData,
119 "show-data-on-hover": this.showDataOnHover,
120 "show-data-axis": this.showDataAxis,
121 "show-labels": this.showLabels,
122 "stacked": this.stacked,
123 };
124
125 if (this.dataSpacing){
126 propClasses[ "data-spacing-" + this.dataSpacing ] = true;
127 }
128
129 let propClassesString = Object.keys(propClasses).reduce((carry, chartClass) => {
130 if (propClasses[chartClass]){
131 carry += " " + chartClass;
132 }
133 return carry;
134 }, "");
135
136 let chartClasses = `charts-css ${this.type} ` + propClassesString + " " + ( this.classes ? this.classes : '' );
137
138 return chartClasses.trim();
139 },
140
141 datasetsCount()
142 {
143 return this.datasets.length;
144 },
145
146 hasHeading()
147 {
148 return !!this.$slots.heading;
149 },
150
151 /**
152 * Converts from datasets schema to Charts.CSS rendering.
153 * @return {array}
154 */
155 rows()
156 {
157 /**
158 * get highest value in values, so we can calculate scale between 0.0 and 1.0
159 * @type {Number}
160 */
161 const max = Math.max(...this.datasets.reduce((carry, dataset) => {
162 carry = carry.concat(dataset.values);
163 return carry;
164 }, []));
165
166 const chartType = this.type;
167
168 return this.datasets.reduce((carry, dataset, index) => {
169
170 /**
171 * Map dataset to each column
172 */
173 dataset.values.forEach((value, valueIndex) => {
174 if (typeof carry[valueIndex] === "undefined"){
175 carry[valueIndex] = [];
176 }
177
178 let tooltip = this.resolveDataTooltip && this.showTooltips ?
179 this.resolveDataTooltip(value, this.labels[valueIndex], dataset.name, valueIndex, valueIndex, this.datasets.length > 1) :
180 null
181 ;
182
183 let mappedValue = {
184 valueRaw: value,
185 valueIndex: valueIndex,
186 datasetName: dataset.name,
187 datasetIndex: index,
188 label: this.labels[valueIndex],
189 tooltip: tooltip,
190 };
191
192 if (chartType === "column" || chartType === "bar"){
193 mappedValue.size = value / max;
194 }
195
196 if (chartType === "area" || chartType === "line"){
197 mappedValue.start = value / max;
198 mappedValue.size = dataset.values[valueIndex + 1] ? dataset.values[valueIndex + 1] / max : 0;
199 }
200
201 carry[valueIndex].push(mappedValue);
202 });
203
204 return carry;
205 }, []);
206 },
207 },
208
209 methods: {
210 /**
211 * Returns the mapped rendering CSS style for the given value, row and column.
212 * @param value
213 * @param rowIndex
214 * @param colIndex
215 * @return {{"--size", "--start"}}
216 */
217 resolveDataStyle(value, rowIndex, colIndex)
218 {
219 let style = {
220 '--start': value.start,
221 '--size': value.size,
222 };
223
224 if (this.resolveDataColor){
225 const color = this.resolveDataColor(value, value.label, value.datasetName, rowIndex, colIndex, this.datasetsCount > 1);
226
227 if (color){
228 style["--color"] = color;
229 }
230 }
231
232 return style;
233 }
234 },
235 }
236</script>