PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.6.2
Independent Analytics – WordPress Analytics Plugin v2.6.2
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / javascript-source / controllers / chart_controller.js
independent-analytics / javascript-source / controllers Last commit date
campaign_builder_controller.js 2 years ago chart_controller.js 2 years ago chart_geo_controller.js 2 years ago chart_interval_controller.js 2 years ago clipboard_controller.js 2 years ago copy_report_controller.js 2 years ago create_report_controller.js 2 years ago dates_controller.js 2 years ago delete_data_controller.js 2 years ago delete_report_controller.js 2 years ago easepick_controller.js 2 years ago export_reports_controller.js 2 years ago filters_controller.js 2 years ago group_controller.js 2 years ago import_reports_controller.js 2 years ago migration_redirect_controller.js 2 years ago modal_controller.js 2 years ago plugin_group_options_controller.js 2 years ago quick_stats_controller.js 2 years ago real_time_controller.js 2 years ago rename_report_controller.js 2 years ago report_controller.js 2 years ago reset_analytics_controller.js 2 years ago save_report_controller.js 2 years ago select_input_controller.js 2 years ago set_favorite_report_controller.js 2 years ago sort_controller.js 2 years ago sortable_reports_controller.js 2 years ago table_columns_controller.js 2 years ago
chart_controller.js
389 lines
1 import {Controller} from "@hotwired/stimulus"
2 import corsairPlugin from '../chart_plugins/corsair_plugin'
3 import {Chart, registerables} from 'chart.js'
4 import color from 'color'
5
6 Chart.register(...registerables);
7
8 export default class extends Controller {
9 static targets = ['primaryMetricSelect', 'secondaryMetricSelect', 'adaptiveWidthSelect'];
10
11 static values = {
12 labels: Array,
13 data: Object,
14 locale: String,
15 currency: {
16 type: String,
17 default: 'USD'
18 },
19 isPreview: Boolean,
20 isUsingWooCommerce: Boolean,
21 primaryChartMetricId: String,
22 primaryChartMetricName: String,
23 secondaryChartMetricId: String,
24 secondaryChartMetricName: String,
25 }
26
27 metricGroups = [{
28 metrics: ['views', 'visitors', 'sessions'],
29 format: 'int'
30 }, {
31 metrics: ['average_session_duration'],
32 format: 'time'
33 }, {
34 metrics: ['bounce_rate'],
35 format: 'percent'
36 }, {
37 metrics: ['views_per_session'],
38 format: 'float'
39 }, {
40 metrics: ['wc_orders', 'wc_refunds'],
41 format: 'int'
42 }, {
43 metrics: ['wc_gross_sales', 'wc_refunded_amount', 'wc_net_sales'],
44 format: 'whole_currency'
45 }, {
46 metrics: ['wc_conversion_rate'],
47 format: 'percent'
48 }, {
49 metrics: ['wc_earnings_per_visitor'],
50 format: 'currency'
51 }, {
52 metrics: ['wc_average_order_volume'],
53 format: 'whole_currency'
54 }, {
55 metrics: ['form_submissions'],
56 prefix_to_include: 'form_submissions_for_',
57 format: 'int'
58 }, {
59 metrics: ['form_conversion_rate'],
60 prefix_to_include: 'form_conversion_rate_for_',
61 format: 'percent'
62 }]
63
64 getLocale() {
65 // Validate the locale
66 try {
67 new Intl.NumberFormat(this.localeValue)
68
69 return this.localeValue
70 } catch (e) {
71 return 'en-US'
72 }
73 }
74
75 hasSecondaryMetric() {
76 return this.hasSecondaryChartMetricIdValue && this.secondaryChartMetricIdValue
77 }
78
79 tooltipTitle(tooltip) {
80 const label = JSON.parse(tooltip[0].label)
81
82 return label.tooltipLabel
83 }
84
85 getGroupByMetricId(metricId) {
86 return this.metricGroups.find(group => {
87 return group.metrics.includes(metricId) || (group.prefix_to_include && metricId.startsWith(group.prefix_to_include))
88 })
89 }
90
91 formatValueForMetric(metricId, value) {
92 const group = this.getGroupByMetricId(metricId)
93
94 switch (group.format) {
95 case 'whole_currency':
96 return new Intl.NumberFormat(this.localeValue, {
97 style: 'currency',
98 currency: this.currencyValue,
99 minimumFractionDigits: 0,
100 maximumFractionDigits: 0,
101 }).format(value);
102 case 'percent':
103 return new Intl.NumberFormat(this.localeValue, {
104 style: 'percent',
105 }).format(value / 100);
106 case 'time':
107 const minutes = Math.floor(value / 60);
108 const seconds = value % 60
109
110 return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
111 default:
112 return value
113 }
114 }
115
116 tooltipLabel = (tooltip) => {
117 if (typeof tooltip.dataset.tooltipLabel === 'function') {
118 return tooltip.dataset.tooltipLabel(tooltip)
119 }
120
121 return tooltip.dataset.label + ': ' + this.formatValueForMetric(tooltip.dataset.id, tooltip.raw)
122 }
123
124 tickText(value) {
125 const label = JSON.parse(this.getLabelForValue(value))
126
127 return label.tick
128 }
129
130 /**
131 * This works because we have a separate hidden select with a single option. When we set the newly
132 * selected option as it's only option, we can see exactly how long the select needs to be
133 */
134 updateMetricSelectWidth(element) {
135 const option = element.options[element.selectedIndex]
136
137 this.adaptiveWidthSelectTarget[0].innerHTML = option.innerText
138 element.style.width = this.adaptiveWidthSelectTarget.getBoundingClientRect().width + 'px'
139 element.parentElement.classList.add('visible');
140 }
141
142 hasSharedAxis(metricId, otherMetricId) {
143 const group = this.getGroupByMetricId(metricId)
144 const otherGroup = this.getGroupByMetricId(otherMetricId)
145
146 return JSON.stringify(group) === JSON.stringify(otherGroup)
147 }
148
149 connect() {
150 if(!this.isPreviewValue) {
151 this.updateMetricSelectWidth(this.primaryMetricSelectTarget)
152 this.updateMetricSelectWidth(this.secondaryMetricSelectTarget)
153 }
154 this.createChart()
155 this.updateChart()
156 }
157
158 changePrimaryMetric(e) {
159 const element = e.target
160 this.primaryChartMetricIdValue = element.value
161 this.primaryChartMetricNameValue = element.options[element.selectedIndex].innerText
162 this.updateMetricSelectWidth(element)
163 this.updateChart()
164
165 Array.from(this.secondaryMetricSelectTarget.querySelectorAll('option')).forEach((option) => {
166 option.toggleAttribute('disabled', option.value === element.value)
167 })
168
169 document.dispatchEvent(
170 new CustomEvent('iawp:changePrimaryChartMetric', {
171 detail: {
172 primaryChartMetricId: element.value
173 }
174 })
175 )
176 }
177
178 changeSecondaryMetric(e) {
179 const element = e.target
180 const hasSelectedSecondaryMetric = element.value !== ''
181
182 if (hasSelectedSecondaryMetric) {
183 this.secondaryChartMetricIdValue = element.value
184 this.secondaryChartMetricNameValue = element.options[element.selectedIndex].innerText
185 } else {
186 this.secondaryChartMetricIdValue = ''
187 this.secondaryChartMetricNameValue = ''
188 }
189
190 this.updateMetricSelectWidth(element)
191 this.updateChart()
192
193 Array.from(this.primaryMetricSelectTarget.querySelectorAll('option')).forEach((option) => {
194 option.toggleAttribute('disabled', option.value === element.value)
195 })
196
197 document.dispatchEvent(
198 new CustomEvent('iawp:changeSecondaryChartMetric', {
199 detail: {
200 secondaryChartMetricId: hasSelectedSecondaryMetric ? element.value : null
201 }
202 })
203 )
204 }
205
206 updateChart() {
207 const primaryDataset = window.iawp_chart.data.datasets[0]
208
209 primaryDataset.id = this.primaryChartMetricIdValue
210 primaryDataset.data = this.dataValue[this.primaryChartMetricIdValue]
211 primaryDataset.label = this.primaryChartMetricNameValue
212
213 // Always start by removing the secondary dataset
214 if (window.iawp_chart.data.datasets.length > 1) {
215 window.iawp_chart.data.datasets.pop()
216 }
217
218 if (this.hasSecondaryMetric()) {
219 const id = this.secondaryChartMetricIdValue
220 const name = this.secondaryChartMetricNameValue
221 const data = this.dataValue[id]
222 const axisId = this.hasSharedAxis(this.primaryChartMetricIdValue, id) ? 'y' : 'defaultRight'
223
224 window.iawp_chart.data.datasets.push(
225 this.makeDataset(id, name, data, axisId, 'rgba(246,157,10)')
226 )
227 }
228
229 window.iawp_chart.update();
230 }
231
232 makeDataset(id, name, data, axisId, colorValue, isPrimary = false) {
233 const accentColor = color(colorValue)
234
235 return {
236 id: id,
237 label: name,
238 data: data,
239 borderColor: accentColor.string(),
240 backgroundColor: accentColor.alpha(.1).string(),
241 pointBackgroundColor: accentColor.string(),
242 tension: 0.4,
243 yAxisID: axisId,
244 fill: true,
245 order: isPrimary ? 1 : 0, // Stack orange on top of purple
246 }
247 }
248
249 createChart() {
250 Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
251 const element = document.getElementById('myChart');
252 const labels = this.labelsValue
253
254 const primaryMetricDataset = this.makeDataset(
255 this.primaryChartMetricIdValue,
256 this.primaryChartMetricNameValue,
257 this.dataValue[this.primaryChartMetricIdValue],
258 'y',
259 'rgba(108,70,174)',
260 true
261 )
262
263 const data = {
264 labels: labels,
265 datasets: ([
266 primaryMetricDataset
267 ]).filter((dataset) => dataset !== null)
268 };
269
270 const options = {
271 locale: this.getLocale(),
272 interaction: {
273 intersect: false,
274 mode: 'index'
275 },
276 scales: {
277 y: {
278 grid: {
279 color: document.body.classList.contains('iawp-dark-mode') && !this.isPreviewValue ? '#9a95a6' : '#DEDAE6',
280 borderColor: '#DEDAE6',
281 tickColor: '#DEDAE6',
282 display: true,
283 drawOnChartArea: true,
284 borderDash: [2, 4]
285 },
286 beginAtZero: true,
287 suggestedMax: 10,
288 ticks: {
289 color: document.body.classList.contains('iawp-dark-mode') && !this.isPreviewValue ? '#ffffff' : '#6D6A73',
290 font: {
291 size: 14,
292 weight: 400,
293 },
294 precision: 0,
295 callback: (value, index, values) => {
296 return this.formatValueForMetric(this.primaryChartMetricIdValue, value)
297 },
298 },
299 },
300 defaultRight: {
301 position: 'right',
302 display: 'auto',
303 grid: {
304 color: document.body.classList.contains('iawp-dark-mode') && !this.isPreviewValue ? '#9a95a6' : '#DEDAE6',
305 borderColor: '#DEDAE6',
306 tickColor: '#DEDAE6',
307 display: true,
308 drawOnChartArea: false,
309 borderDash: [2, 4]
310 },
311 beginAtZero: true,
312 suggestedMax: 10,
313 ticks: {
314 color: document.body.classList.contains('iawp-dark-mode') && !this.isPreviewValue ? '#ffffff' : '#6D6A73',
315 font: {
316 size: 14,
317 weight: 400,
318 },
319 precision: 0,
320 callback: (value, index, values) => {
321 if (this.hasSecondaryMetric()) {
322 return this.formatValueForMetric(this.secondaryChartMetricIdValue, value)
323 } else {
324 return value
325 }
326 },
327 },
328 },
329 x: {
330 grid: {
331 borderColor: '#DEDAE6',
332 tickColor: '#DEDAE6',
333 display: true,
334 drawOnChartArea: false,
335 },
336 ticks: {
337 color: document.body.classList.contains('iawp-dark-mode') && !this.isPreviewValue ? '#ffffff' : '#6D6A73',
338 autoSkip: true,
339 autoSkipPadding: 16,
340 maxRotation: 0,
341 // maxTicksLimit: 20,
342 font: {
343 size: 14,
344 weight: 400
345 },
346 callback: this.tickText
347 },
348 },
349 },
350 plugins: {
351 mode: String, // 'light' or 'dark'
352 legend: {
353 display: false
354 },
355 corsair: {
356 dash: [2, 4],
357 color: '#777',
358 width: 1
359 },
360 tooltip: {
361 itemSort: (a, b) => {
362 return a.datasetIndex < b.datasetIndex ? -1 : 1
363 },
364 callbacks: {
365 title: this.tooltipTitle,
366 label: this.tooltipLabel,
367 }
368 }
369 },
370 elements: {
371 point: {
372 radius: 4
373 }
374 }
375 }
376
377 const config = {
378 type: 'line',
379 data: data,
380 options: options,
381 plugins: [
382 corsairPlugin
383
384 ],
385 };
386
387 window.iawp_chart = new Chart(element, config);
388 }
389 }