Source

components/diagnostic/DiagnosticsCharts.vue

<template lang="pug">
.diagnostics-charts
    Spinner.mt-2(v-if="isLoading")
    .diagnostics-charts-first(v-if="!isLoading")
      DiagnosticsChart(v-for="chartDataItem in filteredChartDataFirst()" :key="chartDataItem.name" :name="chartDataItem.name" :label="chartDataItem.label" :type="chartDataItem.type"
        :color="chartDataItem.computedColor || chartDataItem.color" :dataItems="chartDataItem.data"
        :options="chartDataItem.options"
        @onSelection="$emit('onSelection')"
        )
    .diagnostics-charts-wrapper(v-if="!isLoading")
      DiagnosticsChart(v-for="chartDataItem in filteredChartDataOthers()" :key="chartDataItem.name" :name="chartDataItem.name" :label="chartDataItem.label" :type="chartDataItem.type"
        :color="chartDataItem.computedColor || chartDataItem.color" :dataItems="chartDataItem.data"
        :options="chartDataItem.options"
        @onSelection="$emit('onSelection')"
        )
</template>

<script>
import { mapGetters } from 'vuex'
import DiagnosticsChart from '@c/diagnostic/DiagnosticsChart.vue'
import { datetimeToTimestamp } from '@/utils/dates.js'
import moment from 'moment'
import envService from '@/services/env-service.js'
import i18n from '@/i18n'
import { getQueryStringValue } from '@/utils/querystring'
import Spinner from '@c/shared/Spinner.vue'
import {
  diagnosticsChartReferenceData,
  defaultSensorsVisibility,
} from '@/services/diagnostics-service.js'
import { createEchartDateValueTooltipFormatter } from '@/utils/echarts.js'
import colors from '@/styles/colors.js'
import { queueOperationOnce } from '@/utils/promise.js'

const colorRed = colors.color_contact_off
const colorGreen = colors.color_contact_on

const immediateDeepWatchHandler = (handler) => {
  return {
    handler() {
      return handler(this)
    },
    deep: true,
    immediate: true,
  }
}

/**
 * Otherwise, multiple watchers compete for the same action
 */
function immediateDeepWatchHandlerForChartUpdate() {
  return immediateDeepWatchHandler((vm) => {
    queueOperationOnce(
      'diagnosticscharts_immediateDeepWatchHandlerForChartUpdate',
      () => {
        //wait required vars (vehicleId/date) to re-compute if needed
        vm.$nextTick(() => {
          vm.updateChartData()
        })
      },
      500,
      {
        clearPreviousTimeout: true,
      }
    )
  })
}

function randomNumber(min, max) {
  return Math.floor(Math.random() * (max - min) + min)
}

const speChartConfigurationExtraOptions = {
  chartType: 'plot',
  order: 5,
  labelAffix: (options, vm) => (vm.isTest ? '(SPE)' : ''),
}
const speChartConfiguration = [
  ['speHarshBraking', 'speHarshBraking', speChartConfigurationExtraOptions],
  [
    'speHarshAcceleration',
    'speHarshAcceleration',
    speChartConfigurationExtraOptions,
  ],
  ['speHarshCornering', 'speHarshCornering', speChartConfigurationExtraOptions],
  ['speExcessiveSpeed', 'speExcessiveSpeed', speChartConfigurationExtraOptions],
]

const canChartConfigurationExtraOptions = {
  chartType: 'line',
  order: 4,
  labelAffix: (options, vm) => (vm.isTest ? '(CAN)' : ''),
}

const canFuelLevelLitersClientParamName = envService.getEnvValue(
  'VUE_APP_DIAGNOSTICS_CAN_FUEL_LEVEL_LITERS_CLIENT_PARAM_NAME',
  'canFuelLevelLiters'
)
const canServiceDistanceClientParamName = envService.getEnvValue(
  'VUE_APP_DIAGNOSTICS_CAN_SERVICE_DISTANCE_CLIENT_PARAM_NAME',
  'canServiceDistanceMeters'
)
const canChartConfiguration = [
  [
    'canRPM',
    'sensorCanRPM',
    {
      ...canChartConfigurationExtraOptions,
      show: true,
      tooltip: {
        formatter: createEchartDateValueTooltipFormatter(
          'diagnostics.chart.units.vrm',
          {
            i18n,
          }
        ),
      },
    },
  ],
  [
    'sensorCanConsumptionLiters',
    'computedConsumptionPer100Km',
    (pos, vm) => {
      let itemColor = colorGreen

      let refConso =
        vm.$store.state.diagnostics.vehicleConfiguration
          .vehicleGlobalConsumptionRef
      if (refConso && pos.computedConsumptionPer100Km > refConso) {
        itemColor = colorRed
      }

      return {
        ...canChartConfigurationExtraOptions,
        isFakeEnabled: false,
        tooltip: {
          formatter: createEchartDateValueTooltipFormatter(
            'diagnostics.chart.units.conso100km',
            { i18n }
          ),
        },
        item: {
          itemStyle: {
            color: itemColor,
          },
        },
        /*
        Echart docs says color prop support a callback function but it doesn't seem to work: https://echarts.apache.org/en/option.html#series-lines.lineStyle
        //Goal: Be able to render the line with multiple colors (green/red)
        lineStyle: {
          color(seriesIndex, dataIndex, data, value){
            //throw new Error('lineStyle' + JSON.stringify({ data, value }))
            //return itemColor
            return 'blue'
          },
        },*/
      }
    },
  ],
  [
    'sensorCanFuelPerc',
    'sensorCanFuelPerc',
    {
      ...canChartConfigurationExtraOptions,
      tooltip: {
        formatter: createEchartDateValueTooltipFormatter('%', { i18n }),
      },
    },
  ],
  [
    'sensorCanFuelLiters',
    'sensorCanFuelLiters',
    {
      ...canChartConfigurationExtraOptions,
      tooltip: {
        formatter: createEchartDateValueTooltipFormatter(
          'diagnostics.chart.units.liters',
          { i18n }
        ),
      },
      /* isEnabled: async () =>
        (!envService.isProduction() &&
          getQueryStringValue(canFuelLevelLitersClientParamName) === '1') ||
        (await authService.getClientParameter(
          canFuelLevelLitersClientParamName
        )) === '1',*/
    },
  ],
  [
    'vehicleDistance',
    'vehicleDistance',
    {
      ...canChartConfigurationExtraOptions,
      tooltip: {
        formatter: createEchartDateValueTooltipFormatter('Km', { i18n }),
      },
    },
  ],
  [
    'sensorCanBrakePedal',
    'sensorCanBrakePedal',
    {
      ...canChartConfigurationExtraOptions,
      chartType: 'plot',
    },
  ],
  [
    'sensorCanBatteryPerc',
    'sensorCanBatteryPerc',
    {
      ...canChartConfigurationExtraOptions,
      tooltip: {
        formatter: createEchartDateValueTooltipFormatter('%', { i18n }),
      },
    },
  ],
  [
    'sensorCanThrottle',
    'sensorCanThrottle',
    {
      ...canChartConfigurationExtraOptions,
      tooltip: {
        formatter: createEchartDateValueTooltipFormatter('%', { i18n }),
      },
    },
  ],
  /*[
    'distanceMeters',
    'canServiceDistanceMeters',
    {
      ...canChartConfigurationExtraOptions,
      chartType: 'line',
      order: 4,
      fakeProbability: () => true,
      fakeValue: () => randomNumber(40, 65),
      isEnabled: async () =>
        (!envService.isProduction() &&
          getQueryStringValue(canServiceDistanceClientParamName) === '1') ||
        (await authService.getClientParameter(
          canServiceDistanceClientParamName
        )) === '1',
    },
  ],*/
]

function durationTorFormatter(params) {
  let start = moment(params.value[0]).format('DD/MM/YYYY HH:mm:ss')
  let end = moment(params.value[1]).format('DD/MM/YYYY HH:mm:ss')
  return `
    ${i18n.t('common.from')}: ${start}
    <br/>
    ${i18n.t('common.to')}: ${end}`
}

export default {
  name: 'DiagnosticsCharts',
  components: {
    DiagnosticsChart,
    Spinner,
  },
  props: {
    vehicleId: {
      type: [String, Number],
      default: '',
    },
    date: {
      type: [String, Date],
      default: '',
    },
    timeFrom: {
      type: String,
      default: '00:00',
    },
    timeTo: {
      type: String,
      default: '23:59',
    },
    isTest: {
      type: Boolean,
      default: getQueryStringValue('test') === '1',
    },
  },
  data() {
    return {
      canChartConfiguration,
      isCanChartConfigurationFiltered: false,
    }
  },
  computed: {
    ...mapGetters({
      positions: 'diagnostics/positions',
      vehicleSpeedAlert: 'diagnostics/vehicleSpeedAlert',
      isCanConfiguredForSelectedVehicle:
        'diagnostics/isCanConfiguredForSelectedVehicle',
    }),
    hasValidSpeedAlert() {
      return !isNaN(this.vehicleSpeedAlert) && this.vehicleSpeedAlert !== -1
    },

    isLoading: {
      set(value) {
        this.$store.state.diagnostics.isLoading = value
      },
      get() {
        return this.$store.state.diagnostics.isLoading
      },
    },

    chronoDataItems() {
      return this.$store.state.diagnostics.chronoData.details || []
    },
    chartsData: {
      set(value) {
        this.$store.state.diagnostics.chartsData = value
      },
      get() {
        return this.$store.state.diagnostics.chartsData
      },
    },
  },
  watch: {
    isLoading() {
      if (this.isLoading) {
        this.$loader.showAlternative()
      } else {
        this.$loader.hideAlternative()
      }
    },

    vehicleId: immediateDeepWatchHandlerForChartUpdate(),
    date: immediateDeepWatchHandlerForChartUpdate(),
    chronoDataItems: {
      handler() {
        this.updateChartData()
      },
      deep: true,
    },
    positions() {
      if (this.positions.length === 0) {
        this.chartsData = []
        this.$forceUpdate()
      }
    },
  },
  created() {
    this.isLoading = true
  },
  mounted() {
    this.$store.state.diagnostics.isTest = this.isTest
    this.$store.state.box.isTest = this.isTest
  },
  destroyed() {
    this.$store.state.diagnostics.isTest = false
    this.$store.state.box.isTest = false
  },
  methods: {
    sortedChartData() {
      if (this.chartsData.length === 0) {
        return []
      }
      return [...this.chartsData].sort((a, b) => {
        return a.order > b.order ? 1 : -1
      })
    },
    filteredChartData() {
      return this.sortedChartData().filter((c) => c.show && c.enabled !== false)
    },
    filteredChartDataFirst() {
      return this.filteredChartData().filter((c) => c.order === 0)
    },
    filteredChartDataOthers() {
      return this.filteredChartData().filter((c) => c.order !== 0)
    },
    /**
     * @todo: Some Can charts will be available only if a client parameter is present
     */
    async updateCanChartConfiguration() {
      if (!this.isCanChartConfigurationFiltered) {
        this.canChartConfiguration = await Promise.all(
          this.canChartConfiguration.map((configArray) => {
            return (async () => {
              let options = configArray[2]

              if (options.isEnabled === undefined) {
                return configArray
              } else {
                let isEnabled = await options.isEnabled()
                if (!isEnabled) {
                  return false
                } else {
                  return configArray
                }
              }
            })()
          })
        )
        this.canChartConfiguration = this.canChartConfiguration.filter(
          (configArray) => configArray !== false
        )
        /*console.log('canChartConfiguration has been updated', {
          originalConfig: canChartConfiguration,
          newConfig: this.canChartConfiguration,
        })*/
      }
    },
    async updateChartData() {
      if (this._isRetrievingData) {
        return
      }
      //console.log('updateChartData')
      this.isLoading = true
      this._isRetrievingData = true
      await this.updateCanChartConfiguration()
      await this.$store.dispatch('diagnostics/updateStore', {
        vehicleId: this.vehicleId,
        date: this.date,
        timeFrom: this.timeFrom,
        timeTo: this.timeTo,
      })
      this._isRetrievingData = false
      this.isLoading = false
      this.updateChart()
    },
    /**
     * Lazy create and retrieves a chart item (Wrapper for chart options)
     * @param {String} name Unique chart item name (i.g tor1)
     * @param {String} chartOptions.chartType (plot/line/duration)
     * @param {String} chartOptions.color i.g grey
     * @param {String} chartOptions.order Used by this component to sort multiple charts
     */
    getChartItem(name, chartOptions = {}) {
      if (!this.chartsData.some((c) => c.name == name)) {
        let chartItem = {
          show: chartOptions.show !== undefined ? chartOptions.show : true,
          label: chartOptions.label || name,
          type: chartOptions.chartType || 'plot',
          data: [],
          color: chartOptions.color || 'grey',
          order: chartOptions.order || 1,
          options: { ...chartOptions },
          category: chartOptions.category || 'mandatory',
          ...(diagnosticsChartReferenceData[name] || {}), //Injects name, label, color, category from reference dataset
          ...(name.includes('tor')
            ? diagnosticsChartReferenceData.torItems.find(
                (i) => i.name == name
              ) || {}
            : {}),
          ...(name.includes('ana')
            ? diagnosticsChartReferenceData.anaItems.find(
                (i) => i.name == name
              ) || {}
            : {}),

          name, //Do not override the name
        }

        //Pick the provided label (i.g Conso) and fallback to a computed label otherwise (i.g ANA 1)
        chartItem.label = chartOptions.label || chartItem.label

        chartItem.label = this.$translation(chartItem.label, chartItem.label)

        //Compute color into an actual color (Echart doesn't support css variables)
        if (chartItem.color.includes('var')) {
          //console.log(getComputedStyle(document.querySelector(':root')).getPropertyValue('--color-speed'))
          chartItem.computedColor = getComputedStyle(
            document.querySelector(':root')
          ).getPropertyValue(
            chartItem.color.split('var(').join('').split(')').join('')
          )
        }

        this.chartsData.push(chartItem)
        /*console.log(
          'Adding chart type',
          name,
          this.chartsData.find((c) => c.name == name)
        )*/
      }
      return this.chartsData.find((c) => c.name == name)
    },
    addChartItemDataItem(chartName, xAxis, yAxis, metadata = {}, options = {}) {
      let chartItem = this.getChartItem(chartName, options)
      chartItem.data.push({
        name: `${chartName}_${xAxis}`,
        value: [xAxis, yAxis, metadata],
        ...(options.item || {}),
      })
    },
    addChronoDataItems() {
      this.chronoDataItems.forEach((item) => {
        /**
         * {"startDate":"2022-03-04 07:12:33","endDate":"2022-03-04 07:14:00","period":"00:01:27","type":"travail","iconPath":"./lib/realtimeMap/assets/picto_chrono/chrono_3.svg","distance":0,"service":"00:01:27","driverName":"","address":"Chemin Du Gué 84830 Sérignan-Du-Comtat","lat":44.185455555556,"lng":4.8415694444444,"periodDrive":"00:00:00","periodWork":"00:01:27","periodRest":"00:00:00","periodAvailable":"00:00:00","zone":"","speed":0}
         */
        this.addChartItemDataItem(
          'chrono',
          datetimeToTimestamp(item.startDate),
          datetimeToTimestamp(item.endDate),
          {
            type: item.typeAlt, //Used for tooltip display
          },
          {
            category: 'chrono',
            enabled:
              this.isTest ||
              this.$rights.hasFeatureRight('diagnostics_chart_chrono'),
            label: this.$t(`diagnostics.chart.category.chrono`),
            chartType: 'duration',
            order: 8,
            item: {
              itemStyle: {
                color: item.color,
              },
            },
            tooltip: {
              formatter: (params) => {
                let start = moment(params.value[0]).format(
                  'DD/MM/YYYY HH:mm:ss'
                )
                let end = moment(params.value[1]).format('DD/MM/YYYY HH:mm:ss')
                return `
                ${this.$t(`location_module.chrono.${params.value[2].type}`)}
                <br/>
                ${this.$t('common.from')}: ${start}
            <br/>
            ${this.$t('common.to')}: ${end}`
              },
            },
          }
        )
      })
    },
    /**
     * Fill TOR/ANA chart datasets using a single position.
     * @param {Object} pos src/services/entities/position-entity.js
     * @param {Object} options.filterCode ana/tor
     * @param {Object} options.chartOptions See: getChartItem options
     */
    addTorAnaDataItems(pos, options = {}) {
      if (this.isTest && randomNumber(0, 1000) < 900) {
        return //If test, render only 10% of GPS positions as ANA/TOR
      }

      let xAxis = pos.timestamp
      let sensorTypes = [
        ['sensorTor', 'tor'],
        ['sensorAna', 'ana'],
      ].filter((item) =>
        options.filterCode ? item[1] == options.filterCode : true
      )
      sensorTypes.forEach((sensorTypeItem) => {
        let sensorTypeCode = sensorTypeItem[1] //ana/tor

        let sensorsArray = pos[sensorTypeItem[0]]

        if (sensorsArray.length === 0) {
          return
        }

        let isAna = sensorTypeCode === 'ana'
        /*if (isAna && this.isTest) {
          sensorsArray = sensorsArray.map((sensorItem) => {
            sensorItem.value = Math.random() * (Math.random() > 0.5 ? -1 : 1) //TEST: Random ANA value
            return sensorItem
          })
        }*/
        if (this.isTest) {
          if (isAna) {
            console.debug({
              display: sensorsArray.filter(
                (sensorItem) => sensorItem.display === true
              ).length,
              noDisplay: sensorsArray.filter(
                (sensorItem) => sensorItem.display === false
              ).length,
              total: sensorsArray.length,
            })
          }
        } else {
          if (!isAna) {
            sensorsArray = sensorsArray.filter(
              (sensorItem) => sensorItem.enabled //TOR: Only if enabled
            )
          }
        }

        sensorsArray
          .filter((sensorItem) => {
            if (
              !isAna &&
              this.$store.state.diagnostics.vehicleConfiguration.boxConfigSensorAssignments.some(
                (sa) => sa.sensorNumber === sensorItem.n && sa.isDurationTOR
              ) &&
              !sensorItem.endTimestamp
            ) {
              return false //filter out invalid duration TORs (consecutive enable/disabled)
            }

            return (
              sensorItem.display === true || sensorItem.display === undefined
            )
          })
          .forEach((sensorItem) => {
            let sensorItemValue = sensorItem.value

            //ANA: Map for chart and check constraints
            if (isAna && sensorItem.normalizedValue !== undefined) {
              sensorItemValue = sensorItem.normalizedValue
            }

            let chartType = isAna ? 'line' : 'plot'

            let formatterHandler = createEchartDateValueTooltipFormatter(
              sensorItem.unit || '',
              { i18n }
            )

            if (
              !isAna &&
              this.$store.state.diagnostics.vehicleConfiguration.boxConfigSensorAssignments.some(
                (sa) => sa.sensorNumber === sensorItem.n && sa.isDurationTOR
              )
            ) {
              sensorItemValue = sensorItem.endTimestamp
              chartType = 'duration'
              formatterHandler = durationTorFormatter
            }

            let item = {}

            if (sensorItem.color) {
              item = {
                itemStyle: {
                  color: sensorItem.color,
                },
              }
            }

            //i.g tor1, ana5, etc
            let sensorCode = `${sensorTypeCode}${sensorItem.n}`

            this.addChartItemDataItem(
              sensorCode,
              xAxis,
              sensorItemValue,
              {},
              {
                show:
                  defaultSensorsVisibility[sensorTypeCode] !== undefined
                    ? defaultSensorsVisibility[sensorTypeCode]
                    : false,
                label:
                  sensorItem.name.charAt(0).toUpperCase() +
                  sensorItem.name.slice(1) +
                  (this.isTest ? ` (${sensorCode.toUpperCase()})` : ''),
                chartType,
                item,
                color: sensorItem.color || 'grey',
                ...(options.chartOptions || {}),
                tooltip: {
                  //@todo: Support i18n the ana unit (unit comes from API) (i.g temps -> time)
                  formatter: formatterHandler,
                },
              }
            )
          })
      })
    },
    /**
     * Used for CAN/SPE
     *
     * @param {Object} pos position-entity
     * @param {String} dataTypes[][0] Chart name (Lazy creation)
     * @param {String} dataTypes[][1] Position attribute name (i.g distance)
     * @param {Boolean} dataTypes[][2].isFakeEnabled Disable fake value
     * @param {Function} dataTypes[][2].fakeProbability Customize fake probability (true: 100%) (false: 0%) (randomNumber(0,100)>50: 50%)
     * @param {Function} dataTypes[][2].fakeValue Customize fake value
     */
    addDataItems(pos, dataTypes = []) {
      let chartOptions = {}

      if (this.isTest) {
        pos = { ...pos } //Can't mutate freezed objects
        dataTypes.forEach((dataType) => {
          chartOptions = dataType[2] || {}
          if (chartOptions.fakeValue === false) {
            return
          }

          if (
            chartOptions.isFakeEnabled !== false && chartOptions.fakeProbability
              ? chartOptions.fakeProbability()
              : randomNumber(0, 1000) > 950
          ) {
            let fakeValue = 50
            if (dataType[1].toLocaleLowerCase().includes('perc')) {
              fakeValue = randomNumber(30, 60)
            } else {
              fakeValue = randomNumber(100, 500)
            }
            fakeValue = chartOptions.fakeValue
              ? chartOptions.fakeValue()
              : fakeValue
            pos[dataType[1]] = fakeValue
          }
        })
      }

      dataTypes.forEach((dataType) => {
        chartOptions = dataType[2] || {}

        if (typeof chartOptions === 'function') {
          chartOptions = chartOptions(pos, this)
        }

        let sensorName = dataType[0].split('_')[0]
        let sensorValue = pos[dataType[1]]

        //Uniquement si existe et l'attribut * est renseigné
        if (sensorValue != null) {
          this.addChartItemDataItem(
            sensorName,
            pos.timestamp,
            sensorValue,
            {},
            {
              show: chartOptions.show !== undefined ? chartOptions.show : false,
              label:
                this.$translation(
                  `diagnostics.chart.category.${sensorName}`,
                  sensorName.toUpperCase()
                ) +
                (chartOptions.labelAffix
                  ? ` ${chartOptions.labelAffix(chartOptions, this)}`
                  : ''),
              ...chartOptions,
            }
          )
        }
      })
    },
    /**
     * Source	Attribut	Type de graphe	Position	Affichage dans le Diag	Paramètre utilisateur
     * SPE	harshBraking	Plot	5	Uniquement si l'objet spe existe et l'attribut harshBraking est renseigné	Non
     * SPE	harshAcceleration	Plot	5	Uniquement si l'objet spe existe et l'attribut harshAcceleration est
     * renseigné	Non
     * SPE	harshCornering	Plot	5	Uniquement si l'objet spe existe et l'attribut harshCornering est renseigné	 * Non
     * SPE	excessiveSpeed	Plot	5	Uniquement  si l'objet spe existe et l'attribut excessiveSpeed est renseigné	* Non
     *
     * @param {*} pos
     */
    addSPEDataItems(pos) {
      this.addDataItems(pos, speChartConfiguration)
    },
    /**
     * Source	Attribut	Type de graphe	Position	Affichage dans le Diag	Paramètre utilisateur
     * CAN |	RPM	| Line	| 4	| Uniquement si le véhicule à l'option CAN	| Non
     * CAN	FuelLevel	Line	4	Uniquement si le véhicule à l'option CAN et l'attribut FuelLevel est renseigné	* * * Non
     * CAN	fuelLevelLiters	Line		Uniquement si le véhicule à l'option CAN et l'attribut fuelLevelLiters est * * renseigné	Oui
     * CAN	vehicleDistance	Line	4	Uniquement si le véhicule à l'option CAN et l'attribut vehicleDistance est * * renseigné	Non
     * CAN	brakePedal	Plot	4	Uniquement si le véhicule à l'option CAN et l'attribut brakePedal est renseigné	* * Non
     * CAN	serviceDistance	Line	4	Uniquement si le véhicule à l'option CAN et l'attribut serviceDistance est * * renseigné	Oui
     *
     * @param {*} pos
     */
    addCANDataItems(pos) {
      this.addDataItems(pos, this.canChartConfiguration)
    },
    updateChart() {
      //Clear chart data
      this.chartsData.length = 0

      //Skip charts if no positions (Including Chrono chart)
      if (this.positions.length === 0) {
        return
      }

      this.addChronoDataItems()

      let lastPosition = null
      this.positions.forEach((pos, index) => {
        let xAxis = pos.timestamp

        //Add normal GPS dataset
        this.addChartItemDataItem(
          'normalGPS',
          xAxis,
          1,
          {
            lat: pos.lat,
            lng: pos.lng,
          },
          {
            item: {
              itemStyle: {
                color:
                  lastPosition?.hasContactOn == pos?.hasContactOn
                    ? colors.color_main
                    : pos.hasContactOn
                    ? colorGreen
                    : colorRed,
              },
            },
            /*toolbox: {
              feature: {
                brush: {
                  icon: {
                    lineX: `path://M 867.72,75.66 C 867.72,75.66 867.72,433.34 867.72,433.34 867.72,433.34 105.03,433.34 105.03,433.34 105.03,433.34 105.03,75.66 105.03,75.66 105.03,75.66 867.72,75.66 867.72,75.66 Z M 867.72,75.66 C 867.72,75.66 867.72,433.34 867.72,433.34 867.72,433.34 105.03,433.34 105.03,433.34 105.03,433.34 105.03,75.66 105.03,75.66 105.03,75.66 867.72,75.66 867.72,75.66 Z M 867.72,75.66 C 867.72,75.66 867.72,433.34 867.72,433.34 867.72,433.34 105.03,433.34 105.03,433.34 105.03,433.34 105.03,75.66 105.03,75.66 105.03,75.66 867.72,75.66 867.72,75.66 Z M 974.00,0.00 C 974.00,0.00 974.00,509.00 974.00,509.00 974.00,509.00 856.47,509.00 856.47,509.00 856.47,509.00 856.47,0.00 856.47,0.00 856.47,0.00 974.00,0.00 974.00,0.00 Z M 974.00,0.00 C 974.00,0.00 974.00,509.00 974.00,509.00 974.00,509.00 856.47,509.00 856.47,509.00 856.47,509.00 856.47,0.00 856.47,0.00 856.47,0.00 974.00,0.00 974.00,0.00 Z M 974.00,0.00 C 974.00,0.00 974.00,509.00 974.00,509.00 974.00,509.00 856.47,509.00 856.47,509.00 856.47,509.00 856.47,0.00 856.47,0.00 856.47,0.00 974.00,0.00 974.00,0.00 Z M 117.53,0.00 C 117.53,0.00 117.53,509.00 117.53,509.00 117.53,509.00 0.00,509.00 0.00,509.00 0.00,509.00 0.00,0.00 0.00,0.00 0.00,0.00 117.53,0.00 117.53,0.00 Z M 117.53,0.00 C 117.53,0.00 117.53,509.00 117.53,509.00 117.53,509.00 0.00,509.00 0.00,509.00 0.00,509.00 0.00,0.00 0.00,0.00 0.00,0.00 117.53,0.00 117.53,0.00 Z M 117.53,0.00 C 117.53,0.00 117.53,509.00 117.53,509.00 117.53,509.00 0.00,509.00 0.00,509.00 0.00,509.00 0.00,0.00 0.00,0.00 0.00,0.00 117.53,0.00 117.53,0.00 Z`,
                  },
                  title: {
                    lineX: this.$t(
                      'diagnostics.chart.brush_toggle_button_title'
                    ),
                  },
                  type: ['lineX'],
                },
              },
            },*/
            tooltip: {
              formatter: (params) => {
                return `${moment(params.value[0]).format('DD/MM/YYYY HH:mm:ss')}
                        <br/>
                        ${this.$t('common.lat')}: ${params.value[2].lat.toFixed(
                  5
                )}
                        <br/>
                        ${this.$t('common.lng')}: ${params.value[2].lng.toFixed(
                  5
                )}
                        `
              },
            },
          }
        )

        let speedAlertItemOptions = {}
        if (this.hasValidSpeedAlert && pos.speed > this.vehicleSpeedAlert) {
          speedAlertItemOptions = {
            item: {
              itemStyle: {
                color: 'red',
              },
              tooltip: {
                formatter: createEchartDateValueTooltipFormatter('Km/h', {
                  i18n,
                  html: `<em style="color:'red'" class="fa fa-exclamation-circle"></em>
                  <br>
                  (${i18n.t('diagnostics.chart.speed_alert_tooltip')}: ${
                    this.vehicleSpeedAlert
                  } Km/h)`,
                }),
              },
            },
          }
        }
        this.addChartItemDataItem(
          'speed',
          xAxis,
          pos.speed,
          {},
          {
            tooltip: {
              formatter: createEchartDateValueTooltipFormatter('Km/h', {
                i18n,
              }),
            },
            ...speedAlertItemOptions,
          }
        )

        if (this.isCanConfiguredForSelectedVehicle) {
          this.addCANDataItems(pos) //4
        }

        this.addSPEDataItems(pos) //5
        this.addTorAnaDataItems(pos, {
          filterCode: 'ana',
          chartOptions: {
            order: 6,
          },
        })
        this.addTorAnaDataItems(pos, {
          filterCode: 'tor',
          chartOptions: {
            order: 7,
          },
        })

        /*this.addDataItems(pos, [
          [
            'distanceMeters',
            'distanceMeters',
            {
              chartType: 'line',
              fakeProbability: () => true,
              fakeValue: () => randomNumber(40, 65),
            },
          ],
        ])*/
        lastPosition = pos
      })
    },
  },
}
</script>

<style lang="scss" scoped>
.diagnostics-charts {
  width: 100%;
}
.diagnostics-charts-wrapper {
  max-height: 250px;
  overflow: auto;
  padding-bottom: 50px;
}
.diagnostics-charts-first {
}
</style>