Add archive page

This commit is contained in:
Фролов 2021-05-28 12:04:50 +05:00
parent f221ae5290
commit 5131259b3f
7 changed files with 352 additions and 269 deletions

View File

@ -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 (
<Tag
//color={color}
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{ marginRight: 3 }}>
<span style={{backgroundColor:color}}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span>&nbsp;{label}</span>
</Tag>
);
}
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 = <Select
mode="multiple"
placeholder="Выберите линии"
value={selectedValues}
//showArrow
bordered={false}
tagRender={tagRender}
onChange={handleLinesSetChange}
style={{
minWidth: "300px",
maxWidth: "400px"
}}
>
{linesCollection.map((line) => (<Option key={line.xAccessorName} value={line.xAccessorName} color={line.color}>{line.label}</Option>))}
</Select>;
const popBar = <Row>
{select}
<Tooltip title="Удалить этот график">
<Button onClick={() => onRemoveChart(config.id)}>X</Button>
</Tooltip>
</Row>
return (
<>
<Popover content={popBar}>
<div>
<ChartTimeArchive
data={data}
yDisplay={config.yDisplay}
lines={lines}
rangeDate={rangeDate}
chartRatio={chartRatio}
/>
</div>
</Popover>
</>);
}

View File

@ -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 && <Loader />}
</>

View File

@ -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 (<ChartTimeBase dataParams={dataParams} options={opt}/>)
}

View File

@ -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<ChartTimeProps> = (props) => {
const [dataParams, setDataParams] = useState<ChartTimeDataParams>({ 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 (<ChartTimeBase dataParams={dataParams} />)
}

View File

@ -16,8 +16,9 @@ Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElemen
const defaultOptions = {
//maintainAspectRatio: false,
aspectRatio:0.45,
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<ChartTimeBaseProps> = (props) => {
export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams}) => {
const chartRef = useRef<HTMLCanvasElement>(null)
const [chart, setChart] = useState<any>()
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(<canvas ref={chartRef} />)
}

View File

@ -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) =>
<Col flex={`${i===0 ? geometry.w1st : geometry.wRest}px`}
key={cfg.id}>
<ArchiveColumn
data={saubData}
rangeDate={rangeDate}
chartRatio={i===0 ? geometry.ratio1st : geometry.ratioRest}
onRemoveChart={onRemoveChart}
onSaveConfig={onSaveConfig}
config={cfg}/>
</Col>)
}
return (<>
<Tooltip title="Добавить график">
<Button
type="primary"
onClick={onAddChart}
disabled={chartsCfgs.length >= 6}>
+
</Button>
</Tooltip>
<ConfigProvider locale={locale}>
<RangePicker
showTime
style={{ margin: '5px 5px', }}
{...other}
/>
onChange = {onChangeRange}
value = {rangeDate}
/>
</ConfigProvider>
)
}
// Выбор "перьев" для графиков - перенести в шапку графика
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) => (<Option key={line.xAccessorName}>{line.label}</Option>))
function handleChange(value) {
console.log(`selected ${value}`);
}
return (
<Select
mode="multiple"
allowClear
style={{ width: '50%' }}
placeholder="Выберите значения"
defaultValue={["wellDepth"]}
onChange={handleChange}
>
{children}
</Select>
)
}
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 (
<>
<Display
label={lineGroup.label}
value={pv}
suffix={lineGroup.linePv?.units} />
<ChartTimeArchive
data={data}
yDisplay={lineGroup.yDisplay}
lines={lines}
interval={interval} />
<ChartTimeOnlineFooter
data={dataLast}
{...lineGroup} />
</>)
}
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 (<>
<div style={{ display: 'flex' }}>
<h2>Архив</h2>
<span style={{ flexGrow: 10 }}></span>
<UserOfWells data={saubData} />
</div>
<hr />
<Button type="primary" style={{
borderRadius: '5px',
font: 'bold',
textAlign: 'center',
margin: '5px 5px',
}}>Добавить график</Button>
<RangePickerLocalized />
<SelectDataCharts />
<Row style={{ marginBottom: '1rem' }}>
<Col>
Интервал:&nbsp;
<Select defaultValue="600" onChange={setChartInterval}>
<Option value='600'>10 минут</Option>
<Option value='1800'>30 минут</Option>
<Option value='3600'>1 час</Option>
<Option value='21600'>6 час</Option>
<Option value='86400'>1 день</Option>
</Select>
</Col>
</Row>
<Row>
<Col span={24 - 2}>
<Row>
{paramsGroups.map(group =>
<Col span={colSpan} className='border_small' key={group.label}>
<Column data={saubData} lineGroup={group} interval={chartInterval} />
</Col>)}
</Row>
</Col>
<Row ref={chartsContainerRef}>
{charts}
</Row>
</>)
}

View File

@ -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)
})
}