diff --git a/src/components/ArchiveColumn.jsx b/src/components/ArchiveColumn.jsx
new file mode 100644
index 0000000..d2f9b1a
--- /dev/null
+++ b/src/components/ArchiveColumn.jsx
@@ -0,0 +1,123 @@
+import { useState, useEffect } from 'react';
+import { Button, Select, Tag, Popover, Row, Tooltip } from 'antd';
+import { ChartTimeArchive } from './charts/ChartTimeArchive';
+//import { SlidersOutlined } from '@ant-design/icons';
+
+const { Option } = Select;
+
+const linesCollection = [
+ { label: "Глубина забоя", xAccessorName: "wellDepth", color: '#f00' },
+ { label: "Положение инструмента", xAccessorName: "bitDepth", color: '#ff0' },
+ { label: "Высота талевого блока", xAccessorName: "blockPosition", color: '#f0f' },
+ { label: "Талевый блок. Мин положение", xAccessorName: "blockPositionMin", color: '#0ff' },
+ { label: "Талевый блок. Макс положение", xAccessorName: "blockPositionMax", color: '#0f0' },
+ { label: "Скорость талевого блока", xAccessorName: "blockSpeed", color: '#00f' },
+
+ { label: "Скорости талевого блока. Задание", xAccessorName: "blockSpeedSp", color: '#c00' },
+ { label: "Талевый блок. Задание скорости для роторного бурения", xAccessorName: "blockSpeedSpRotor", color: '#cc0' },
+ { label: "Талевый блок. Задание скорости для режима слайда", xAccessorName: "blockSpeedSpSlide", color: '#c0c' },
+ { label: "Талевый блок. Задание скорости для проработки", xAccessorName: "blockSpeedSpDevelop", color: '#0cc' },
+ { label: "Давление", xAccessorName: "pressure", color: '#0c0' },
+ { label: "Давление. Холостой ход", xAccessorName: "pressureIdle", color: '#00c' },
+
+ { label: "Давление. Задание", xAccessorName: "pressureSp", color: '#900' },
+ { label: "Давление. Задание для роторного бурения", xAccessorName: "pressureSpRotor", color: '#990' },
+ { label: "Давление. Задание для режима слайда", xAccessorName: "pressureSpSlide", color: '#909' },
+ { label: "Давление. Задание для проработки", xAccessorName: "pressureSpDevelop", color: '#099' },
+ { label: "Давление дифф. Аварийное макс.", xAccessorName: "pressureDeltaLimitMax", color: '#090' },
+ { label: "Осевая нагрузка", xAccessorName: "axialLoad", color: '#009' },
+
+ { label: "Осевая нагрузка. Задание", xAccessorName: "axialLoadSp", color: '#600' },
+ { label: "Осевая нагрузка. Аварийная макс.", xAccessorName: "axialLoadLimitMax", color: '#660' },
+ { label: "Вес на крюке", xAccessorName: "hookWeight", color: '#606' },
+ { label: "Вес на крюке. Холостой ход", xAccessorName: "hookWeightIdle", color: '#066' },
+ { label: "Вес на крюке. Посадка", xAccessorName: "hookWeightLimitMin", color: '#060' },
+ { label: "Вес на крюке. Затяжка", xAccessorName: "hookWeightLimitMax", color: '#006' },
+
+ { label: "Момент на роторе", xAccessorName: "rotorTorque", color: '#300' },
+ { label: "Момент на роторе. Холостой ход", xAccessorName: "rotorTorqueIdle", color: '#330' },
+ { label: "Момент на роторе. Задание", xAccessorName: "rotorTorqueSp", color: '#303' },
+ { label: "Момент на роторе. Аварийный макс.", xAccessorName: "rotorTorqueLimitMax", color: '#033' },
+ { label: "Обороты ротора", xAccessorName: "rotorSpeed", color: '#030' },
+ { label: "Расход", xAccessorName: "flow", color: '#003' },
+
+ { label: "Расход. Холостой ход", xAccessorName: "flowIdle", color: '#666' },
+ { label: "Расход. Аварийный макс.", xAccessorName: "flowDeltaLimitMax", color: '#ccc' },
+]
+
+const tagRender = ({ label, value, closable, onClose }) =>{
+ const onPreventMouseDown = event => {
+ event.preventDefault();
+ event.stopPropagation();
+ };
+
+ let color = linesCollection.find(l=>l.xAccessorName === value)?.color
+ return (
+
+
+ {label}
+
+ );
+}
+
+export function ArchiveColumn({ data, config, rangeDate, chartRatio, onRemoveChart, onSaveConfig }) {
+ const [lines, setLines] = useState([]);
+
+ useEffect(() => {
+ setLines(config.lines);
+ },[config]);
+
+ const handleLinesSetChange = (linesKeys) => {
+ let newLines = linesCollection.filter(line => linesKeys.includes(line.xAccessorName));
+ config.lines = newLines;
+ if(onSaveConfig)
+ onSaveConfig()
+ setLines(newLines);
+ };
+
+ let selectedValues = lines?.map(line=>line.xAccessorName)??[]
+
+ const select = ;
+
+ const popBar =
+ {select}
+
+
+
+
+
+ return (
+ <>
+
+
+
+
+
+
+ >);
+}
diff --git a/src/components/WellTreeSelector.jsx b/src/components/WellTreeSelector.jsx
index f8cf7f0..32e8531 100644
--- a/src/components/WellTreeSelector.jsx
+++ b/src/components/WellTreeSelector.jsx
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'
+import { useParams } from 'react-router-dom'
import { WellService } from '../services/api'
import Loader from '../components/Loader'
import { TreeSelect } from 'antd'
@@ -38,6 +39,7 @@ export default function WellTreeSelector(props) {
const [wellsTree, setWellsTree] = useState([])
const [loader, setLoader] = useState(false)
const history = useHistory()
+ let { id } = useParams();
let updateWellsList = async () => {
setLoader(true)
@@ -72,6 +74,7 @@ export default function WellTreeSelector(props) {
treeData={wellsTree}
treeDefaultExpandAll
onSelect={onSelect}
+ value = {id}
/>
{loader && }
>
diff --git a/src/components/charts/ChartTimeArchive.jsx b/src/components/charts/ChartTimeArchive.jsx
new file mode 100644
index 0000000..042a236
--- /dev/null
+++ b/src/components/charts/ChartTimeArchive.jsx
@@ -0,0 +1,73 @@
+import moment from 'moment';
+import { useEffect, useState} from 'react';
+import {ChartTimeBase} from './ChartTimeBase'
+
+const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
+
+const CreateDataset = (lineConfig) => {
+ let color = lineConfig.borderColor
+ ?? lineConfig.backgroundColor
+ ?? lineConfig.color
+ ?? GetRandomColor()
+
+ let dataset = {
+ label: lineConfig.label,
+ data: [],
+ backgroundColor: lineConfig.backgroundColor ?? color,
+ borderColor: lineConfig.borderColor ?? color,
+ borderWidth: lineConfig.borderWidth ?? 1,
+ borderDash: lineConfig.dash ?? [],
+ }
+ return dataset
+}
+
+const ChartOptions = {
+ responsive: true,
+ // plugins:{
+ // legend:{
+ // maxHeight: 64,
+ // fullSize: true,
+ // display: true,
+ // posision:'chartArea',
+ // align: 'start',
+ // }
+ // }
+}
+
+export const ChartTimeArchive = ({lines, data, yDisplay, rangeDate, chartRatio}) => {
+ const [dataParams, setDataParams] = useState({data:{datasets:[]}})
+
+ useEffect(() => {
+ if ((!lines)
+ || (!data))
+ return
+
+ let newDatasets = lines.map(lineCfg => {
+ let dataset = CreateDataset(lineCfg)
+ dataset.data = data.map(dataItem => {
+ return {
+ x: dataItem[lineCfg.xAccessorName],
+ y: new Date(dataItem[lineCfg.yAccessorName??'date'])
+ }
+ })
+ return dataset
+ })
+
+ let interval = rangeDate ? (rangeDate[1] - rangeDate[0]) / 1000 : null
+ let startDate = rangeDate ? rangeDate[0] : moment()
+ let newParams = {
+ yInterval: interval,
+ yStart: startDate,
+ displayLabels: yDisplay??false,
+ data: {
+ datasets: newDatasets
+ }
+ }
+ setDataParams(newParams)
+ }, [data, lines, yDisplay, rangeDate, chartRatio])
+
+ const opt = ChartOptions
+ opt.aspectRatio = chartRatio
+
+ return ()
+}
diff --git a/src/components/charts/ChartTimeArchive.tsx b/src/components/charts/ChartTimeArchive.tsx
deleted file mode 100644
index ec4fb6d..0000000
--- a/src/components/charts/ChartTimeArchive.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { useEffect, useState} from 'react';
-import {ChartTimeBase, ChartTimeData, ChartTimeDataParams} from './ChartTimeBase'
-
-const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
-
-
-function GetOrCreateDatasetByLineConfig(data: ChartTimeData, lineConfig: LineConfig) {
- let dataset = data?.datasets.find(d => d.label === lineConfig.label)
- if (!dataset) {
- let color = lineConfig.borderColor
- ?? lineConfig.backgroundColor
- ?? lineConfig.color
- ?? GetRandomColor()
-
- dataset = {
- label: lineConfig.label,
- data: [],
- backgroundColor: lineConfig.backgroundColor ?? color,
- borderColor: lineConfig.borderColor ?? color,
- borderWidth: lineConfig.borderWidth ?? 1,
- borderDash: lineConfig.dash ?? [],
- }
- data.datasets.push(dataset);
- }
- return dataset
-}
-
-export type LineConfig = {
- type?: string
- label: string
- units?: string
- xAccessorName: string
- yAccessorName: string
- color?: string
- borderColor?: string
- backgroundColor?: string
- borderWidth?: number
- dash?: number[]
- labels?: any[]
-}
-
-export type ChartTimeProps = {
- label?: string,
- yDisplay: Boolean,
- lines: LineConfig[],
- data: any[],
- interval: number,
-}
-
-export const ChartTimeArchive: React.FC = (props) => {
- const [dataParams, setDataParams] = useState({ data: { datasets: [] }, yStart: new Date(), })
-
- useEffect(() => {
- if ((!props?.lines)
- || (!props?.data)
- || (props.lines.length === 0)
- || (props.data.length === 0))
- return
-
- setDataParams((preDataParams) => {
- props.lines.forEach(lineCfg => {
- let dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
- let points = props.data.map(dataItem => {
- return {
- x: dataItem[lineCfg.xAccessorName],
- y: new Date(dataItem[lineCfg.yAccessorName])
- }
- })
- dataset.data = points;
- });
-
- preDataParams.yStart = new Date()
- preDataParams.yStart.setSeconds(preDataParams.yStart.getSeconds() - props.interval)
- preDataParams.yInterval = props.interval
- preDataParams.displayLabels = props.yDisplay
- return preDataParams
- })
-
- }, [props.data, props.lines, props.interval, props.yDisplay])
-
- return ()
-}
\ No newline at end of file
diff --git a/src/components/charts/ChartTimeBase.tsx b/src/components/charts/ChartTimeBase.tsx
index 3ab6234..9fc607b 100644
--- a/src/components/charts/ChartTimeBase.tsx
+++ b/src/components/charts/ChartTimeBase.tsx
@@ -16,8 +16,9 @@ Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElemen
const defaultOptions = {
//maintainAspectRatio: false,
- aspectRatio:0.45,
- animation: false,
+ aspectRatio: 0.45,
+ animation: false,
+ events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
y:{
type: 'time',
@@ -38,12 +39,13 @@ const defaultOptions = {
year: 'yyyy.MM',
},
},
- position:{ x: 20 },
+ //position:'center',
grid:{
drawTicks: false,
},
ticks: {
z: 1,
+ display : false,
textStrokeColor : "#ffff",
textStrokeWidth : 2,
color:"#000",
@@ -98,49 +100,49 @@ const timeUnitByInterval = (intervalSec:number):String =>{
return 'year'
}
-export const ChartTimeBase: React.FC = (props) => {
+export const ChartTimeBase: React.FC = ({options, dataParams}) => {
const chartRef = useRef(null)
-
const [chart, setChart] = useState()
useEffect(()=>{
- if(chartRef.current && (!chart)){
+ if((chartRef.current)&&(!chart)){
let thisOptions = {}
- Object.assign(thisOptions, defaultOptions, props.options)
+ Object.assign(thisOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current, {
type: 'line',
options: thisOptions,
- data: props.dataParams.data
+ data: dataParams.data
})
setChart(newChart)
+
return () => chart?.destroy()
}
- }, [chart, props.options, props.dataParams])
+ }, [chart, options, dataParams])
useEffect(()=>{
if(!chart)
return
- chart.data = props.dataParams.data
+ chart.data = dataParams.data
+ chart.options.aspectRatio = options?.aspectRatio
- if(props.dataParams.yStart){
- let interval = Number(props.dataParams.yInterval ?? 600)
- let start = new Date(props.dataParams.yStart)
- let end = new Date(props.dataParams.yStart)
+ if(dataParams.yStart){
+ let interval = Number(dataParams.yInterval ?? 600)
+ let start = new Date(dataParams.yStart)
+ let end = new Date(dataParams.yStart)
end.setSeconds(end.getSeconds() + interval)
-
if(chart.options.scales?.y){
chart.options.scales.y.max = end.getTime()
chart.options.scales.y.min = start.getTime()
- chart.options.scales.y.ticks.display = props.dataParams.displayLabels ?? true
+ chart.options.scales.y.ticks.display = dataParams.displayLabels ?? true
chart.options.scales.y.time.unit = timeUnitByInterval(interval)
- chart.options.scales.y.time.stepSize = Math.round(interval/24)
+ chart.options.scales.y.time.stepSize = Math.round(interval/32)
}
}
chart.update()
- }, [chart, props])
+ }, [chart, dataParams, options])
return()
}
\ No newline at end of file
diff --git a/src/pages/Archive.jsx b/src/pages/Archive.jsx
index 54285b5..036da4b 100644
--- a/src/pages/Archive.jsx
+++ b/src/pages/Archive.jsx
@@ -1,188 +1,139 @@
-import { useState, useEffect } from 'react'
+import { useRef, useLayoutEffect, useState, useEffect } from 'react'
import {
Button,
DatePicker,
ConfigProvider,
- Select,
Row,
Col,
-} from 'antd'
+ Tooltip} from 'antd'
import { useParams } from 'react-router-dom'
-import { Subscribe } from '../services/signalr'
import { DataService } from '../services/api'
-import 'moment/locale/ru'
import locale from 'antd/lib/locale/ru_RU'
-import { ChartTimeArchive } from '../components/charts/ChartTimeArchive'
-import { Display } from '../components/Display'
-import { ChartTimeOnlineFooter } from '../components/ChartTimeOnlineFooter'
-import { UserOfWells } from '../components/UserOfWells'
-
+import {generateUUID} from '../services/UidGenerator'
+import { ArchiveColumn } from '../components/ArchiveColumn'
+import moment from 'moment'
const { RangePicker } = DatePicker;
-const { Option } = Select;
-// Выбор периода времени
-const RangePickerLocalized = ({locale, ...other}) => {
- return (
+const SaveObject = (key, obj) => {
+ let json = JSON.stringify(obj)
+ localStorage.setItem(key, json)
+}
+
+const LoadObject = (key) => {
+ let json = localStorage.getItem(key)
+ return json ? JSON.parse(json) : null
+}
+
+export default function Archive() {
+ let { id } = useParams();
+ const [saubData, setSaubData] = useState([])
+ const [chartsCfgs, setChartsCfgs] = useState([])
+ const [rangeDate, setRangeDate] = useState([moment().subtract(3,'hours'), moment()])
+ const [geometry, setGeometry] = useState({ratioRest:1, ratio1st:1, wRest:.5, w1st:.5})
+ const chartsCfgsKey = 'chartsCfgs'
+ const chartsContainerRef = useRef();
+
+ const handleReceiveDataSaub = (data) => {
+ if (data)
+ setSaubData(data)
+ }
+
+ const onAddChart = () => {
+ let newChartCfgs = [...chartsCfgs, {id: generateUUID(), yDisplay: false, aspectRatio:1}]
+ setChartsCfgs(newChartCfgs)
+ }
+
+ const onRemoveChart = (id) => {
+ let newChartCfgs = chartsCfgs.filter(cfg => cfg.id !== id )
+ setChartsCfgs(newChartCfgs)
+ }
+
+ const onSaveConfig = ()=>{
+ SaveObject(chartsCfgsKey, chartsCfgs)
+ }
+
+ const onChangeRange = (range) => {
+ setRangeDate(range)
+ }
+
+ useLayoutEffect(()=>{
+ if(chartsContainerRef.current && chartsCfgs?.length){
+ let w = chartsContainerRef.current.offsetWidth //1792
+ w = w > 0 ? w : 1792
+ let ot = chartsContainerRef.current.offsetTop
+ let ph = chartsContainerRef.current.offsetParent.offsetHeight
+ let h = ph - ot - 32 //761
+ h = h > 0 ? h : 761
+
+ let chartsCount = chartsCfgs.length
+
+ let labelLenght = 8
+ let borderWidth = 8
+ let wRest = Math.floor((w - labelLenght)/chartsCount) - borderWidth
+ let w1st = wRest + labelLenght
+
+ let ratio1st = w1st/h
+ let ratioRest = wRest/h
+
+ setGeometry({ratio1st, ratioRest, w1st, wRest})
+ }
+ },[chartsContainerRef, chartsCfgs])
+
+ useEffect(() => {
+ let cfgs = LoadObject(chartsCfgsKey)
+ if(cfgs)
+ setChartsCfgs(cfgs)
+ },[])
+
+ useEffect(()=>{
+ SaveObject(chartsCfgsKey, chartsCfgs)
+ },[chartsCfgs])
+
+ useEffect(() => {
+ let interval = (rangeDate[1] - rangeDate[0]) / 1000
+ let startDate = rangeDate[0].toISOString()
+
+ DataService.getData(id, startDate, interval, 2048)
+ .then(handleReceiveDataSaub)
+ .catch(error => console.error(error))
+ }, [id, rangeDate]);
+
+ let charts = null
+ if(chartsCfgs.length > 0){
+ chartsCfgs[0].yDisplay = true
+
+ charts = chartsCfgs.map((cfg, i) =>
+
+
+ )
+ }
+
+ return (<>
+
+
+
+ onChange = {onChangeRange}
+ value = {rangeDate}
+ />
- )
-}
-
-// Выбор "перьев" для графиков - перенести в шапку графика
-const SelectDataCharts = () => {
- const linesCollection = [
- { label: "Глубина забоя", xAccessorName: "wellDepth", color: '#a0a' },
- { label: "Положение инструмента", xAccessorName: "bitDepth", color: '#a0a' },
- { label: "Высота талевого блока", xAccessorName: "blockPosition", color: '#a0a' },
- { label: "Талевый блок. Мин положение", xAccessorName: "blockPositionMin", color: '#a0a' },
- { label: "Талевый блок. Макс положение", xAccessorName: "blockPositionMax", color: '#a0a' },
- { label: "Скорость талевого блока", xAccessorName: "blockSpeed", color: '#a0a' },
- { label: "Скорости талевого блока. Задание", xAccessorName: "blockSpeedSp", color: '#a0a' },
- { label: "Талевый блок. Задание скорости для роторного бурения", xAccessorName: "blockSpeedSpRotor", color: '#a0a' },
- { label: "Талевый блок. Задание скорости для режима слайда", xAccessorName: "blockSpeedSpSlide", color: '#a0a' },
- { label: "Талевый блок. Задание скорости для проработки", xAccessorName: "blockSpeedSpDevelop", color: '#a0a' },
- { label: "Давление", xAccessorName: "pressure", color: '#a0a' },
- { label: "Давление. Холостой ход", xAccessorName: "pressureIdle", color: '#a0a' },
- { label: "Давление. Задание", xAccessorName: "pressureSp", color: '#a0a' },
- { label: "Давление. Задание для роторного бурения", xAccessorName: "pressureSpRotor", color: '#a0a' },
- { label: "Давление. Задание для режима слайда", xAccessorName: "pressureSpSlide", color: '#a0a' },
- { label: "Давление. Задание для проработки", xAccessorName: "pressureSpDevelop", color: '#a0a' },
- { label: "Давление дифф. Аварийное макс.", xAccessorName: "pressureDeltaLimitMax", color: '#a0a' },
- { label: "Осевая нагрузка", xAccessorName: "axialLoad", color: '#a0a' },
- { label: "Осевая нагрузка. Задание", xAccessorName: "axialLoadSp", color: '#a0a' },
- { label: "Осевая нагрузка. Аварийная макс.", xAccessorName: "axialLoadLimitMax", color: '#a0a' },
- { label: "Вес на крюке", xAccessorName: "hookWeight", color: '#a0a' },
- { label: "Вес на крюке. Холостой ход", xAccessorName: "hookWeightIdle", color: '#a0a' },
- { label: "Вес на крюке. Посадка", xAccessorName: "hookWeightLimitMin", color: '#a0a' },
- { label: "Вес на крюке. Затяжка", xAccessorName: "hookWeightLimitMax", color: '#a0a' },
- { label: "Момент на роторе", xAccessorName: "rotorTorque", color: '#a0a' },
- { label: "Момент на роторе. Холостой ход", xAccessorName: "rotorTorqueIdle", color: '#a0a' },
- { label: "Момент на роторе. Задание", xAccessorName: "rotorTorqueSp", color: '#a0a' },
- { label: "Момент на роторе. Аварийный макс.", xAccessorName: "rotorTorqueLimitMax", color: '#a0a' },
- { label: "Обороты ротора", xAccessorName: "rotorSpeed", color: '#a0a' },
- { label: "Расход", xAccessorName: "flow", color: '#a0a' },
- { label: "Расход. Холостой ход", xAccessorName: "flowIdle", color: '#a0a' },
- { label: "Расход. Аварийный макс.", xAccessorName: "flowDeltaLimitMax", color: '#a0a' },
- ]
-
- const children = linesCollection.map((line) => ())
-
- function handleChange(value) {
- console.log(`selected ${value}`);
- }
-
- return (
-
- )
-}
-
-const Column = ({ lineGroup, data, interval }) => {
- let lines = [lineGroup.linePv]
-
- if (lineGroup.lineSp)
- lines.push(lineGroup.lineSp)
-
- let dataLast = null
- let pv = null
- if (data?.length > 0) {
- dataLast = data[data.length - 1];
- if (lineGroup.linePv)
- pv = dataLast[lineGroup.linePv?.xAccessorName]
- }
- return (
- <>
-
-
-
- >)
-}
-
-const paramsGroups = []
-
-export default function Archive(props) {
- let { id } = useParams();
- const [saubData, setSaubData] = useState([])
- const [chartInterval, setChartInterval] = useState(600)
-
- const handleReceiveDataSaub = (data) => {
- if (data) {
- setSaubData(data)
- }
- }
-
- useEffect(() => {
- DataService.getData(id)
- .then(handleReceiveDataSaub)
- .catch(error => console.error(error))
-
- let unSubscribeMessages = Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub)
- return () => {
- unSubscribeMessages()
- }
- }, [id]);
-
- const colSpan = 24 / (paramsGroups.length)
-
- return (<>
-
-
Архив
-
-
-
-
-
-
-
-
-
- Интервал:
-
-
-
-
-
-
- {paramsGroups.map(group =>
-
-
- )}
-
-
+
+ {charts}
>)
}
\ No newline at end of file
diff --git a/src/services/UidGenerator.ts b/src/services/UidGenerator.ts
new file mode 100644
index 0000000..4b58ec0
--- /dev/null
+++ b/src/services/UidGenerator.ts
@@ -0,0 +1,13 @@
+export function generateUUID():string {
+ let seed = (25869874412483
+ * new Date().getTime()
+ * (performance && performance.now && (performance.now()))) % 173395562924509
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ let random = Math.random()
+ seed = seed > 272
+ ? seed = seed / 17
+ : seed = (seed * random * random * 557833831325167) % 173395562924509
+ random = Math.floor((random * seed) % 16)
+ return (c === 'x' ? random : (random & 0xB)).toString(16)
+ })
+}
\ No newline at end of file