Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

time scale component #16

Merged
merged 7 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function typescriptRules() {
return {
function typescriptRules(annotations) {
let result = {
"no-console": "error",
"@typescript-eslint/ban-types": [
"error",
Expand Down Expand Up @@ -43,23 +43,26 @@ function typescriptRules() {
}
}
],
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true,
}
],
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/typedef": [
};
if (annotations !== false) {
result["@typescript-eslint/typedef"] = [
"error",
{
arrowParameter: true,
memberVariableDeclaration: true,
parameter: true,
propertyDeclaration: true,
}
],
};
]
result["@typescript-eslint/explicit-function-return-type"] = [
"error",
{
"allowExpressions": true,
}
]
}
return result;
}

module.exports = {
Expand Down Expand Up @@ -112,6 +115,7 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
],
files: ['**/*.svelte'],
excludedFiles: ['**/samples/**/*.svelte'],
env: {
browser: true,
node: false
Expand All @@ -120,6 +124,27 @@ module.exports = {
settings: {
'svelte3/typescript': require('typescript'),
}
},
{
parser: '@typescript-eslint/parser',
plugins: [
'svelte3',
'@typescript-eslint',
],
processor: 'svelte3/svelte3',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
files: ['**/samples/**/*.svelte'],
env: {
browser: true,
node: false
},
rules: typescriptRules(false),
settings: {
'svelte3/typescript': require('typescript'),
}
}
]
};
23 changes: 23 additions & 0 deletions src/demo/app.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
SeriesActionParams
} from 'svelte-lightweight-charts/types';
import type {ChartActionParams} from 'svelte-lightweight-charts';
import type {$$EVENTS as TimeScaleEvents} from 'svelte-lightweight-charts/components/time-scale.svelte';

import {LineStyle} from 'lightweight-charts';
import {chart} from 'svelte-lightweight-charts';
import {BAR_DATA, HISTOGRAM_DATA, LINE_DATA} from './data-series';
Expand All @@ -29,6 +31,7 @@
import CandlestickSeries from 'svelte-lightweight-charts/components/candlestick-series.svelte';
import BaselineSeries from 'svelte-lightweight-charts/components/baseline-series.svelte';
import PriceLine from 'svelte-lightweight-charts/components/price-line.svelte';
import TimeScale from 'svelte-lightweight-charts/components/time-scale.svelte'

type EverySeriesApi =
| ISeriesApi<'Area'>
Expand Down Expand Up @@ -96,6 +99,7 @@

let showVolume = true;
let intraday = false;
let timeScaleVisible = true;
let ticker: number | null = null;

$: if (ticker !== null) {
Expand Down Expand Up @@ -357,6 +361,12 @@
function handleVolumeComponentReference(ref: ISeriesApi<'Histogram'> | null): void {
volumeComponent = ref;
}

let timeScaleInfo: Record<string, unknown> = {};
function handleTimeScaleEvent<T extends keyof TimeScaleEvents>(event: TimeScaleEvents[T]): void {
timeScaleInfo[event.type] = event.detail;
timeScaleInfo = { ...timeScaleInfo };
}
</script>

<form>
Expand Down Expand Up @@ -402,6 +412,9 @@
<label>
<input type="checkbox" name="intraday" id="intraday" bind:checked={intraday}> Intraday
</label>
<label>
<input type="checkbox" name="time-scale" id="time-scale" bind:checked={timeScaleVisible}> Visible Time Scale
</label>
<button on:click={handleTicker} type="button">{ ticker ? 'Stop' : 'Start' }</button>
<button on:click={handleFitContent} type="button">Fit content</button>
</fieldset>
Expand All @@ -423,6 +436,12 @@
ref: console.log
}}
>
<TimeScale
visible={timeScaleVisible}
on:visibleTimeRangeChange={handleTimeScaleEvent}
on:visibleLogicalRangeChange={handleTimeScaleEvent}
on:sizeChange={handleTimeScaleEvent}
/>
{#if mainProps.type === 'Area' }
<AreaSeries
{...(mainProps.options ?? {})}
Expand Down Expand Up @@ -501,6 +520,10 @@
</Chart>
</fieldset>
{/if}
<fieldset>
<legend>TimeScale info:</legend>
<pre>{JSON.stringify(timeScaleInfo, null, 4)}</pre>
</fieldset>
</form>


Expand Down
136 changes: 136 additions & 0 deletions src/demo/samples/infinite-history.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<h1>Infinite History</h1>
<div class="container">
<Chart width={600} height={300}>
<TimeScale
ref={handleTimeScaleRef}
on:visibleLogicalRangeChange={handleVisibleLogicalRangeChange}
/>
<CandlestickSeries
ref={handleSeriesRef}
data={data}
/>
</Chart>
</div>

<script>
import Chart from 'svelte-lightweight-charts/components/chart.svelte';
import CandlestickSeries from 'svelte-lightweight-charts/components/candlestick-series.svelte';
import TimeScale from 'svelte-lightweight-charts/components/time-scale.svelte';

let timeScale;
let candleSeries;

const period = {
timeFrom: { day: 1, month: 1, year: 2018 },
timeTo: { day: 1, month: 1, year: 2019 },
};
let data = generateBarsData(period);

let timer = null;

function handleVisibleLogicalRangeChange() {
if (!timeScale || !candleSeries) {
return;
}
if (timer !== null) {
return;
}
timer = setTimeout(() => {
const logicalRange = timeScale.getVisibleLogicalRange();
if (logicalRange !== null) {
const barsInfo = candleSeries.barsInLogicalRange(logicalRange);
if (barsInfo !== null && barsInfo.barsBefore < 10) {
const firstTime = getBusinessDayBeforeCurrentAt(data[0].time, 1);
const lastTime = getBusinessDayBeforeCurrentAt(firstTime, Math.max(100, -barsInfo.barsBefore + 100));
const newPeriod = {
timeFrom: lastTime,
timeTo: firstTime,
};
data = [...generateBarsData(newPeriod), ...data];
candleSeries.setData(data);
}
}
timer = null;
}, 500);
}

function handleTimeScaleRef(api) {
timeScale = api;
}
function handleSeriesRef(api) {
candleSeries = api;
}

function getBusinessDayBeforeCurrentAt(date, daysDelta) {
const dateWithDelta = new Date(Date.UTC(date.year, date.month - 1, date.day - daysDelta, 0, 0, 0, 0));
return { year: dateWithDelta.getFullYear(), month: dateWithDelta.getMonth() + 1, day: dateWithDelta.getDate() };
}

function generateBarsData(period) {
const res = [];
const controlPoints = generateControlPoints(res, period);
for (let i = 0; i < controlPoints.length - 1; i++) {
const left = controlPoints[i];
const right = controlPoints[i + 1];
fillBarsSegment(left, right, res);
}
return res;
}

function fillBarsSegment(left, right, points) {
const deltaY = right.price - left.price;
const deltaX = right.index - left.index;
const angle = deltaY / deltaX;
for (let i = left.index; i <= right.index; i++) {
const basePrice = left.price + (i - left.index) * angle;
const openNoise = (0.1 - Math.random() * 0.2) + 1;
const closeNoise = (0.1 - Math.random() * 0.2) + 1;
const open = basePrice * openNoise;
const close = basePrice * closeNoise;
const high = Math.max(basePrice * (1 + Math.random() * 0.2), open, close);
const low = Math.min(basePrice * (1 - Math.random() * 0.2), open, close);
points[i].open = open;
points[i].high = high;
points[i].low = low;
points[i].close = close;
}
}

function generateControlPoints(res, period, dataMultiplier) {
let time = period !== undefined ? period.timeFrom : { day: 1, month: 1, year: 2018 };
const timeTo = period !== undefined ? period.timeTo : { day: 1, month: 1, year: 2019 };
const days = getDiffDays(time, timeTo);
dataMultiplier = dataMultiplier || 1;
const controlPoints = [];
controlPoints.push({ index: 0, price: getRandomPrice() * dataMultiplier });
for (let i = 0; i < days; i++) {
if (i > 0 && i < days - 1 && Math.random() < 0.05) {
controlPoints.push({ index: i, price: getRandomPrice() * dataMultiplier });
}
res.push({ time: time });
time = nextBusinessDay(time);
}
controlPoints.push({ index: res.length - 1, price: getRandomPrice() * dataMultiplier });
return controlPoints;
}

function getDiffDays(dateFrom, dateTo) {
const df = convertBusinessDayToUTCTimestamp(dateFrom);
const dt = convertBusinessDayToUTCTimestamp(dateTo);
const diffTime = Math.abs(dt.getTime() - df.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}

function convertBusinessDayToUTCTimestamp(date) {
return new Date(Date.UTC(date.year, date.month - 1, date.day, 0, 0, 0, 0));
}

function nextBusinessDay(time) {
const d = convertBusinessDayToUTCTimestamp({ year: time.year, month: time.month, day: time.day + 1 });
return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
}

function getRandomPrice() {
return 10 + Math.round(Math.random() * 10000) / 100;
}
</script>
Loading