I'm currently using a TimeTickStrategy on the xAxis to show data flowing in real time. However, it's formatting the data using the unix epoch as {hours:minutes:seconds}.
Below is my xAxis code:
xAxis1 = chart.getDefaultAxisX()
.setTickStrategy(AxisTickStrategies.Time, (tickStrategy) => tickStrategy.setTimeOrigin(this.originTime))
.setScrollStrategy(AxisScrollStrategies.progressive)
.setMouseInteractions(false)
.setInterval(60000 * -15, 60000) //xAxis Range(Min,Max) = (origin - 15 minutes, origin + 1 minute)
I'd like to have that formatted in the local time, but I didn't see any options for that when I was reading through the TimeTickStrategy documentation:
https://www.arction.com/lightningchart-js-api-documentation/v3.4.0/classes/timetickstrategy.html
Example (MST):
457050:14:51 --> 11:14:51 AM
Is there a way to do this?
Seems like TimeTickStrategy has no configuration options for formatting.
Maybe you can use DateTimeTickStrategy? It's a bit clumsy but it has quite extensive configuration methods. Here's some kind of an example I whipped up.
/*
* LightningChartJS example showcasing the TimeTickStrategy feature with scrolling data and axis.
*/
// Extract required parts from LightningChartJS.
const {
lightningChart,
AxisScrollStrategies,
AxisTickStrategies,
Themes,
emptyTick,
} = lcjs
// Import data-generators from 'xydata'-library.
const {
createProgressiveTraceGenerator
} = xydata
const chart = lightningChart().ChartXY({
theme: Themes.darkGold,
})
.setTitle('Scrolling TimeTickStrategy example')
.setPadding({ right: 40 })
const axisX = chart
.getDefaultAxisX()
// Enable TimeTickStrategy for X Axis.
.setTickStrategy(AxisTickStrategies.DateTime, ticks => ticks
.setGreatTickStyle(emptyTick)
.setFormattingSecond(
undefined,
{ hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true },
(x) => 'minor'
)
)
// Configure progressive ScrollStrategy.
.setScrollStrategy(AxisScrollStrategies.progressive)
// Set view to 15 seconds
.setInterval(-1 * 15 * 1000, 0)
.setAnimationScroll(false)
const axisY = chart.getDefaultAxisY()
.setAnimationScroll(false)
// Add 3 series for real-time signal monitoring.
const seriesList = new Array(3).fill(0).map((_, iSeries) =>
chart
.addLineSeries({
dataPattern: {
pattern: 'ProgressiveX',
},
})
)
const legend = chart.addLegendBox().add(chart)
// Dispose example UI elements automatically if they take too much space. This is to avoid bad UI on mobile / etc. devices.
.setAutoDispose({
type: 'max-width',
maxWidth: 0.30,
})
// Stream live timestamp data into series.
// Application displays timestamps as offset from when application started (starts at 00:00:00).
const timeOrigin = Date.now()
Promise.all(
seriesList.map((_) =>
createProgressiveTraceGenerator()
.setNumberOfPoints(60 * 1000)
.generate()
.toPromise(),
),
).then((data) => {
let dataAmount = 0
const pushData = () => {
const nDataPoints = 1
seriesList.forEach((series, iSeries) => {
const seriesData = data[iSeries]
const seriesPoints = []
for (let i = 0; i < nDataPoints; i += 1) {
seriesPoints.push({
// TimeTickStrategy interprets values as milliseconds (UNIX timestamp).
// Exactly same as JavaScript Date APIs.
x: Date.now() - timeOrigin,
y: seriesData[(dataAmount + i) % seriesData.length].y,
})
}
series.add(seriesPoints)
})
dataAmount += nDataPoints
requestAnimationFrame(pushData)
}
pushData()
})
<script src="https://unpkg.com/#arction/lcjs#3.4.0/dist/lcjs.iife.js"></script>
<script src="https://unpkg.com/#arction/xydata#1.2.1/dist/xydata.iife.js"></script>
Related
I built the following frame using vis.js timeline:
But I'd like to keep only the bottom part (highlighted in blue). Is there a way to achieve that?
Respectively code:
export const TimelineGraph = (props) => {
const items = props.items;
const container = useRef(null);
const options = {
showCurrentTime: false
};
useEffect(() => {
const timeline = container.current && new Timeline(container.current, items, options);
}, [container, items]);
return (
<div
ref={container}
className={'container'}
/>
);
};
I think I found a solution. You just need to add min and max options to the options object, like so:
const options = {
showCurrentTime: false,
min: getLowestDate(),
max: getHighestDate(),
};
After that, the upper bar was gone.
Hi all trying to write some logic in paper.js (also using opentype.js for font data) so that when a number contains two or more consecutive zeros' the zero path is divided so that it is solid.
Things i know a zero path, using my particular font, is made up of an outer path with 19 segments and an inner path made up of 18 segments
So I thought would try to iterate over all paths check if a path has 19 segments and the next path has 18 segments and call path.unite() which kind of works. But I only want it to do this with consecutive '0' eg '100', '1000' but not 10.
So i was trying to do an if else statment where if-else (the current path has 18 segments and the next path is less than 18 segments) if true then do nothin or call path.divide()?
Im sure there is a way better way of doing this. Can you help please.
link to codepen
paper.install(window);
window.onload = () => {
paper.setup("canvas");
opentype.load(
"https://assets.codepen.io/1070/pphatton-ultralight-webfont.woff",
(err, font) => {
if (err) {
console.log(err);
} else {
const fontPath = font.getPath("10k", 0, 100, 100).toSVG();
const count = new paper.CompoundPath(fontPath);
count.unite();
count.children.forEach((child, i) => {
if (
child.segments.length === 19 &&
count.children[i + 1].segments.length === 18
) {
const eye = child.unite();
eye.selected = true;
} else if(
count.children[i + 1].segments.length === 18
&& child.segments.length < 18
) {
console.log('hello');
// const target = child.divide();
count.children[i].fillColor = 'black'
} else{
}
});
// const flatCount = count.children[1].unite()
// console.log(count.children[2].segments.length)
// const flatCountTwo = count.children[5].unite()
// flatCount.translate(5,0)
count.fillColor = "red";
project.activeLayer.fitBounds(view.bounds.scale(0.6));
}
}
);
};
I think that rather than using Font.getPath to retrieve a single svg path for the whole text, you should use Font.getPaths to retrieve an svg path for each character.
This way you can quite simply do your analysis on the input string directly and handle the consecutive 0 differently than other characters.
Edit
In order to detect the consecutive zeros, yes, you could use a regex or loop over the characters, like I did in the following example.
Here's a fiddle showcasing a possible solution.
const handleZero = (path) => {
path.children = path.children.slice(0, 1);
};
const getConsecutiveZerosIndices = (content) => {
const zero = '0';
return [...content]
.map((char, i) => ({ char, i }))
.filter(({ char, i }) => {
const previousCharacter = content?.[i - 1];
const nextCharacter = content?.[i + 1];
return char === zero && (previousCharacter === zero || nextCharacter === zero);
})
.map(({ i }) => i);
};
const drawText = (content, font) => {
const fontPaths = font.getPaths(content, 0, 100, 100);
const consecutiveZerosIndices = getConsecutiveZerosIndices(content);
const paths = fontPaths.map((fontPath, i) => {
const path = new paper.CompoundPath(fontPath.toSVG());
if (consecutiveZerosIndices.includes(i)) {
handleZero(path);
}
return path;
});
const group = new paper.Group(paths);
group.fillColor = 'red';
return group;
};
const draw = (font) => {
const path1 = drawText('10k', font);
const path2 = drawText('100k', font);
const path3 = drawText('1000k', font);
path2.position = path1.position.add(0, path1.bounds.height * 1.2);
path3.position = path2.position.add(0, path2.bounds.height * 1.2);
paper.project.activeLayer.fitBounds(paper.view.bounds.scale(0.6));
};
paper.setup('canvas');
opentype.load(
'https://assets.codepen.io/1070/pphatton-ultralight-webfont.woff',
(err, font) => draw(font)
);
I am adding custom formtable like below
return builder
.addRow(name)
.addRow(price)
Is there anyway to style the row ? I want to display the price with big font and different color.
thank you.
Here is an example of simple custom autoCursor
// Extract required parts from LightningChartJS.
const {
lightningChart,
AutoCursorModes,
UIElementBuilders,
UILayoutBuilders,
UIBackgrounds,
ColorHEX,
SolidFill,
SolidLine,
UIOrigins,
translatePoint,
} = lcjs;
// Import data-generator from 'xydata'-library.
const {
createProgressiveTraceGenerator
} = xydata
// colors of the series
const colors = ["#fc03f0", "#1cb843", "#eeff00", "#0955ff", "#500c3f"];
// Create a XY Chart.
const chart = lightningChart()
.ChartXY({
// theme: Themes.dark
})
// Disable native AutoCursor to create custom
.setAutoCursorMode(AutoCursorModes.disabled)
.setTitle("Custom Cursor using LCJS UI");
// set title for Y axis
chart.getDefaultAxisY().setTitle("Y-axis");
// generate data and creating the series
const series = chart.addLineSeries().setStrokeStyle(
new SolidLine({
fillStyle: new SolidFill({ color: ColorHEX(colors[0]) }),
thickness: 2,
})
);
createProgressiveTraceGenerator()
.setNumberOfPoints(200)
.generate()
.toPromise()
.then((data) => {
return series.add(data);
});
// Create UI elements for custom cursor.
const resultTable = chart
.addUIElement(
UILayoutBuilders.Column.setBackground(UIBackgrounds.Rectangle),
{
x: chart.getDefaultAxisX(),
y: chart.getDefaultAxisY(),
}
)
.setMouseInteractions(false)
.setOrigin(UIOrigins.LeftBottom)
.setMargin(2)
.setBackground((background) =>
background.setStrokeStyle(
new SolidLine({
thickness: 1,
fillStyle: new SolidFill({ color: ColorHEX("#c9bab9") }),
})
)
);
const rowX = resultTable
.addElement(UILayoutBuilders.Row)
.addElement(UIElementBuilders.TextBox)
.setTextFont((font) => font.setSize(15))
.setTextFillStyle(new SolidFill({ color: ColorHEX(colors[1]) }));
const rowY = resultTable
.addElement(UILayoutBuilders.Row)
.addElement(UIElementBuilders.TextBox)
.setTextFont((font) => font.setSize(15))
.setTextFillStyle(new SolidFill({ color: ColorHEX(colors[2]) }));
// Hide custom cursor components initially.
resultTable.dispose();
// Implement custom cursor logic with events.
chart.onSeriesBackgroundMouseMove((_, event) => {
const mouseLocationClient = { x: event.clientX, y: event.clientY };
// Translate mouse location to LCJS coordinate system for solving data points from series, and translating to Axes.
const mouseLocationEngine = chart.engine.clientLocation2Engine(
mouseLocationClient.x,
mouseLocationClient.y
);
// Translate mouse location to Axis.
const mouseLocationAxis = translatePoint(
mouseLocationEngine,
chart.engine.scale,
series.scale
);
// Solve nearest data point to the mouse on each series.
const nearestDataPoints = series.solveNearestFromScreen(mouseLocationEngine)
if (nearestDataPoints) {
// Set custom cursor location.
resultTable.setPosition({
x: nearestDataPoints.location.x,
y: nearestDataPoints.location.y,
});
// set Origin of resultTable
if ( nearestDataPoints.location.x > chart.getDefaultAxisX().getInterval().end / 1.5 ) {
if (nearestDataPoints.location.y >chart.getDefaultAxisY().getInterval().end / 1.5) {
resultTable.setOrigin(UIOrigins.RightTop);
} else {
resultTable.setOrigin(UIOrigins.RightBottom);
}
} else if ( nearestDataPoints.location.y > chart.getDefaultAxisY().getInterval().end / 1.5) {
resultTable.setOrigin(UIOrigins.LeftTop);
} else {
resultTable.setOrigin(UIOrigins.LeftBottom);
}
// Format result table text.
rowX.setText(`X: ${nearestDataPoints.location.x.toFixed(1)}`);
rowY.setText(`Y: ${nearestDataPoints.location.y.toFixed(1)}`)
// Display cursor.
resultTable.restore();
} else {
// Hide cursor.
resultTable.dispose();
}
});
chart.onSeriesBackgroundMouseLeave((_, e) => {
resultTable.dispose();
});
<script src="https://unpkg.com/#arction/xydata#1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#3.0.0/dist/lcjs.iife.js"></script>
There is no existing built-in method for customizing each row/column of result table apart from the text content.
This is definitely a feature that we are eventually including in the library, as soon as it becomes a priority or a customer requests it.
Right now, as a community user, it can be done by creating a custom cursor. Unfortunately there aren't many examples on this subject.
The general idea is solving the nearest data point from mouse location with ChartXY.solveNearest (or calculating by some custom method), and showing the custom cursor using UI elements and custom ticks if desired.
The ResultTable can be created by combining Column and Row layouts to get a grid, and then adding TextBox elements, whose font/color you can style individually.
EDIT: The official examples for custom cursors have been released.
You can find them there https://www.arction.com/lightningchart-js-interactive-examples/search.html?t=cursor
There is one example with LCJS UI elements and another with dynamic HTML & CSS (same approach could be used with some external UI framework).
I have been trying to use the riot games api to compute all the previous custom games and then find the win loss streaks for individual players, I have built the following code to grab matches for a particular user.
See https://github.com/FriendlyUser/deno-riot-games-custom-games
But I feel like the riot games api is only returning data with its v4 api up to season 11, if anyone could clarify how the api works or explain how I could possibly get more data, that would be fantastic.
import { writeJson } from "https://deno.land/std/fs/mod.ts"
import "https://deno.land/x/dotenv/load.ts"
const player_id = Deno.env.get('ACCOUNT_ID')
const region_url = 'https://na1.api.riotgames.com'
let riot_URL = new URL(`${region_url}/lol/match/v4/matchlists/by-account/${player_id}`)
enum HTTP {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE'
}
interface MatchlistDto {
startIndex: number
totalGames: number
endIndex: number
matches: Array<any>
}
function makeFetchOptions(
riotKey = Deno.env.get('RIOT_API_KEY'),
method: HTTP = HTTP.GET
): object {
return {
method: method,
headers: {
"Accept-Charset": "application/x-www-form-urlencoded; charset=UTF-8",
"Accept-Language": "en-US,en;q=0.9",
'X-Riot-Token': riotKey
}
}
}
function appendMatchHistory(riot_endpoint: string): Promise<MatchlistDto> {
const riotKey = Deno.env.get('RIOT_API_KEY')
console.log(riotKey)
const options = makeFetchOptions(riotKey)
return fetch(riot_endpoint, options)
.then( (resp: any) => {
console.log(resp)
return resp.json()
})
.then( (matchData: MatchlistDto) => {
return matchData
})
}
const max_iterations = 1000
let bIndex = 0
let eIndex = 100
let current_url = riot_URL
let riot_endpoint = null
let allMatches = []
let customGames = []
const sleep = (milliseconds: number) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
for (let i = 0; i < max_iterations; i++) {
console.log(`beginIndex: ${bIndex} endIndex: ${eIndex}`)
riot_endpoint = current_url.toString()
const newMatches = await appendMatchHistory(riot_endpoint)
await sleep(1500)
current_url.searchParams.delete('beginIndex')
current_url.searchParams.delete('endIndex')
const {matches} = newMatches
if (matches.length == 0) {
console.log(`ENDING SCRIPT AT ${eIndex} with ${matches.length}`)
break
}
// startIndex becomes endIndex
bIndex = eIndex
eIndex = eIndex + 100
allMatches.push(newMatches.matches)
// get new url
current_url.searchParams.append('beginIndex', String(bIndex))
current_url.searchParams.append('endIndex', String(eIndex))
}
await writeJson(
"./allData.json",
allMatches
);
Sorry if this answer is late. But yes the Riot API is only for "current" data, and that is why sites like U.GG, OP.GG, etc actually run scripts to store data continuously. So to get statistics you would have to write scripts to store it into your own DB over time.
Sadly, there is no way to get previous season data
I'm trying to crawl movie data from TMDB website. I finished my code with pure javascript, but I want to change the code into functional programming style by using ramda.js.
I attached my code below. I want to get rid of for-loop (if it is possible) and use R.pipe function.
(async () => {
for (let i = 0; i < 1000; i++) {
(() => {
setTimeout(async () => {
let year = startYr + Math.floor(i / 5);
await request.get(path(year, i % 5 + 1), async (err, res, data) => {
const $ = cheerio.load(data);
let list = $('.results_poster_card .poster.card .info .flex a');
_.forEach(list, (element, index) => {
listJSON.push({
MovieID: $(element).attr('id').replace('movie_', ''),
Rank: (i % 5) * 20 + index + 1,
Year: year
});
});
if(i === 1000 - 1) {
await pWriteFile(`${outputPath}/movieList.json`, JSON.stringify(listJSON, null, 2));
}
});
}, 1000 * i);
})(i);
}
})().catch(error => console.log(error));
Steps:
1- Break your code in small functions
2- Stop using async await and use promise.then(otherFunction)
3- When using promise, you could create a sleep function like these: const sleep = (time) => new Promise(resolve => setTimeout(resolve, time));
Ex.:
const process = index => sleep(1000)
.then(() => makeRequest(index))
.then(processData);
R.range(0, 1000)
.reduce(
(prev, actual) => prev.then(() => process(actual),
Promise.resolve()
) // Sequential
.then(printResult);
R.range(0, 1000)
.map(process) // Parallel
.then(printResult);
You can use the Ramda range() function to replace your loop.
https://ramdajs.com/docs/#range
R.range(0, 1000);
That will provide you with a collection of integers (your i) that you can work with (map() or whatever you need).