diff --git a/packages/axes/src/Axis.tsx b/packages/axes/src/Axis.tsx index 5f2151ed2..e710772b9 100644 --- a/packages/axes/src/Axis.tsx +++ b/packages/axes/src/Axis.tsx @@ -3,7 +3,6 @@ import { getAxisCanvas, GenericChartComponent, getStrokeDasharrayCanvas, - identity, last, strokeDashTypes, } from "@react-financial-charts/core"; @@ -205,9 +204,7 @@ const tickHelper = (props: AxisProps, scale: ScaleContinuousNumeric tickFormat(d) || ""; + const format = tickFormat === undefined ? scale.tickFormat(tickArguments) : (d: any) => tickFormat(d) || ""; const sign = orient === "top" || orient === "left" ? -1 : 1; const tickSpacing = Math.max(innerTickSize, 0) + tickPadding; diff --git a/packages/axes/src/XAxis.tsx b/packages/axes/src/XAxis.tsx index 5737a632d..6d84349b6 100644 --- a/packages/axes/src/XAxis.tsx +++ b/packages/axes/src/XAxis.tsx @@ -3,7 +3,7 @@ import * as PropTypes from "prop-types"; import * as React from "react"; import { Axis } from "./Axis"; -export interface XAxisProps { +export interface XAxisProps { readonly axisAt?: number | "top" | "bottom" | "middle"; readonly className?: string; readonly domainClassName?: string; @@ -25,7 +25,7 @@ export interface XAxisProps { readonly showTickLabel?: boolean; readonly strokeStyle?: string; readonly strokeWidth?: number; - readonly tickFormat?: (value: number | Date) => string; + readonly tickFormat?: (value: T) => string; readonly tickPadding?: number; readonly tickSize?: number; readonly tickLabelFill?: string; @@ -39,7 +39,7 @@ export interface XAxisProps { readonly zoomCursorClassName?: string; } -export class XAxis extends React.Component { +export class XAxis extends React.Component> { public static defaultProps = { axisAt: "bottom", className: "react-financial-charts-x-axis", diff --git a/packages/core/src/ChartCanvas.tsx b/packages/core/src/ChartCanvas.tsx index 8659aaa11..d1a392568 100644 --- a/packages/core/src/ChartCanvas.tsx +++ b/packages/core/src/ChartCanvas.tsx @@ -865,6 +865,7 @@ export class ChartCanvas extends React.Component { this.waitingForPanAnimationFrame = false; diff --git a/packages/core/src/EventCapture.tsx b/packages/core/src/EventCapture.tsx index 0c9398cbd..0945030bc 100644 --- a/packages/core/src/EventCapture.tsx +++ b/packages/core/src/EventCapture.tsx @@ -493,8 +493,9 @@ export class EventCapture extends React.Component( fallbackEnd: { lastItem: T; lastItemX: TAccessor }, @@ -40,7 +38,7 @@ function extentsWrapper( inputDomain: [TDomain, TDomain], xAccessor: (item: T) => TDomain, initialXScale: ScaleContinuousNumeric | ScaleTime, - { currentPlotData, currentDomain, fallbackStart, fallbackEnd }: any = {}, + { currentPlotData, currentDomain, fallbackStart, fallbackEnd, ignoreThresholds = false }: any = {}, ) { if (useWholeData) { return { plotData: data, domain: inputDomain }; @@ -93,50 +91,21 @@ function extentsWrapper( const chartWidth = last(xScale.range()) - head(xScale.range()); - log( - // @ts-ignore - `Trying to show ${filteredData.length} points in ${width}px,` + - ` I can show up to ${showMaxThreshold(width, pointsPerPxThreshold) - 1} points in that width. ` + - `Also FYI the entire chart width is ${chartWidth}px and pointsPerPxThreshold is ${pointsPerPxThreshold}`, - ); - - if (canShowTheseManyPeriods(width, filteredData.length, pointsPerPxThreshold, minPointsPerPxThreshold)) { + if ( + (ignoreThresholds && filteredData.length > 1) || + canShowTheseManyPeriods(width, filteredData.length, pointsPerPxThreshold, minPointsPerPxThreshold) + ) { plotData = filteredData; domain = realInputDomain; - - // @ts-ignore - log("AND IT WORKED"); } else { if (chartWidth > showMaxThreshold(width, pointsPerPxThreshold) && isDefined(fallbackEnd)) { plotData = filteredData; const newEnd = getNewEnd(fallbackEnd, xAccessor, initialXScale, head(realInputDomain)); domain = [head(realInputDomain), newEnd]; - - const newXScale = xScale.copy().domain(domain) as - | ScaleContinuousNumeric - | ScaleTime; - - const newWidth = Math.floor( - newXScale(xAccessor(last(plotData))) - newXScale(xAccessor(head(plotData))), - ); - - // @ts-ignore - log(`and ouch, that is too much, so instead showing ${plotData.length} in ${newWidth}px`); } else { plotData = - currentPlotData || filteredData.slice(filteredData.length - showMax(width, pointsPerPxThreshold)); - domain = currentDomain || [xAccessor(head(plotData)), xAccessor(last(plotData))]; - - const newXScale = xScale.copy().domain(domain) as - | ScaleContinuousNumeric - | ScaleTime; - - const newWidth = Math.floor( - newXScale(xAccessor(last(plotData))) - newXScale(xAccessor(head(plotData))), - ); - - // @ts-ignore - log(`and ouch, that is too much, so instead showing ${plotData.length} in ${newWidth}px`); + currentPlotData ?? filteredData.slice(filteredData.length - showMax(width, pointsPerPxThreshold)); + domain = currentDomain ?? [xAccessor(head(plotData)), xAccessor(last(plotData))]; } } return { plotData, domain }; @@ -145,7 +114,9 @@ function extentsWrapper( } function canShowTheseManyPeriods(width: number, arrayLength: number, maxThreshold: number, minThreshold: number) { - return arrayLength > showMinThreshold(width, minThreshold) && arrayLength < showMaxThreshold(width, maxThreshold); + const widthAdjustedMinThreshold = showMinThreshold(width, minThreshold); + const widthAdjustedMaxTheshold = showMaxThreshold(width, maxThreshold); + return arrayLength >= widthAdjustedMinThreshold && arrayLength < widthAdjustedMaxTheshold; } function showMinThreshold(width: number, threshold: number) { diff --git a/packages/scales/src/index.ts b/packages/scales/src/index.ts index fcf7f34c0..6a8422ab1 100644 --- a/packages/scales/src/index.ts +++ b/packages/scales/src/index.ts @@ -1,10 +1,11 @@ -import { ScaleContinuousNumeric } from "d3-scale"; +import { ScaleContinuousNumeric, ScaleTime } from "d3-scale"; export { default as discontinuousTimeScaleProvider, discontinuousTimeScaleProviderBuilder, } from "./discontinuousTimeScaleProvider"; export { default as financeDiscontinuousScale } from "./financeDiscontinuousScale"; +export * from "./timeFormat"; -export const defaultScaleProvider = (xScale: ScaleContinuousNumeric) => { - return (data: any, xAccessor: any) => ({ data, xScale, xAccessor, displayXAccessor: xAccessor }); +export const defaultScaleProvider = (xScale: ScaleContinuousNumeric | ScaleTime) => { + return (data: any[], xAccessor: any) => ({ data, xScale, xAccessor, displayXAccessor: xAccessor }); }; diff --git a/packages/scales/src/timeFormat.ts b/packages/scales/src/timeFormat.ts new file mode 100644 index 000000000..c2bc6ce87 --- /dev/null +++ b/packages/scales/src/timeFormat.ts @@ -0,0 +1,29 @@ +import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from "d3-time"; +import { timeFormat as d3TimeFormat } from "d3-time-format"; + +const formatMillisecond = d3TimeFormat(".%L"); +const formatSecond = d3TimeFormat(":%S"); +const formatMinute = d3TimeFormat("%H:%M"); +const formatHour = d3TimeFormat("%H:%M"); +const formatDay = d3TimeFormat("%e"); +const formatWeek = d3TimeFormat("%e"); +const formatMonth = d3TimeFormat("%b"); +const formatYear = d3TimeFormat("%Y"); + +export const timeFormat = (date: Date) => { + return (timeSecond(date) < date + ? formatMillisecond + : timeMinute(date) < date + ? formatSecond + : timeHour(date) < date + ? formatMinute + : timeDay(date) < date + ? formatHour + : timeMonth(date) < date + ? timeWeek(date) < date + ? formatDay + : formatWeek + : timeYear(date) < date + ? formatMonth + : formatYear)(date); +}; diff --git a/packages/stories/src/features/edgeCases/BasicLineSeries.tsx b/packages/stories/src/features/edgeCases/BasicLineSeries.tsx index 3f4811938..f4d5b4a83 100644 --- a/packages/stories/src/features/edgeCases/BasicLineSeries.tsx +++ b/packages/stories/src/features/edgeCases/BasicLineSeries.tsx @@ -35,6 +35,7 @@ class BasicLineSeries extends React.Component { ratio={ratio} width={width} margin={this.margin} + minPointsPerPxThreshold={0.0025} data={data} displayXAccessor={displayXAccessor} seriesName="Data" diff --git a/packages/stories/src/features/scales/Scales.tsx b/packages/stories/src/features/scales/Scales.tsx index 366e10db9..87b38c804 100644 --- a/packages/stories/src/features/scales/Scales.tsx +++ b/packages/stories/src/features/scales/Scales.tsx @@ -1,25 +1,24 @@ import { format } from "d3-format"; -import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from "d3-time"; -import { timeFormat } from "d3-time-format"; -import { scaleTime, ScaleContinuousNumeric } from "d3-scale"; +import { scaleTime, ScaleTime, ScaleContinuousNumeric } from "d3-scale"; import * as React from "react"; -import { Chart, ChartCanvas, XAxis, YAxis, CandlestickSeries, withDeviceRatio, withSize } from "react-financial-charts"; +import { + Chart, + ChartCanvas, + XAxis, + YAxis, + CandlestickSeries, + timeFormat, + withDeviceRatio, + withSize, +} from "react-financial-charts"; import { IOHLCData, withOHLCData } from "../../data"; -const formatMillisecond = timeFormat(".%L"), - formatSecond = timeFormat(":%S"), - formatMinute = timeFormat("%H:%M"), - formatHour = timeFormat("%H:%M"), - formatDay = timeFormat("%e"), - formatWeek = timeFormat("%e"), - formatMonth = timeFormat("%b"), - formatYear = timeFormat("%Y"); - interface ChartProps { readonly data: IOHLCData[]; readonly height: number; readonly width: number; readonly ratio: number; + readonly xScale?: ScaleTime; readonly yScale?: ScaleContinuousNumeric; } @@ -27,13 +26,12 @@ class Scales extends React.Component { private readonly margin = { left: 0, right: 48, top: 0, bottom: 24 }; public render() { - const { data, height, ratio, width, yScale } = this.props; + const { data, height, ratio, width, xScale = scaleTime(), yScale } = this.props; const xAccessor = (d: IOHLCData) => d.date; const start = xAccessor(data[data.length - 1]); const end = xAccessor(data[Math.max(0, data.length - 100)]); const xExtents = [start, end]; - const xScale = scaleTime(); return ( { > - + @@ -59,24 +57,6 @@ class Scales extends React.Component { private readonly yExtents = (data: IOHLCData) => { return [data.high, data.low]; }; - - private readonly timeFormat = (date: Date) => { - return (timeSecond(date) < date - ? formatMillisecond - : timeMinute(date) < date - ? formatSecond - : timeHour(date) < date - ? formatMinute - : timeDay(date) < date - ? formatHour - : timeMonth(date) < date - ? timeWeek(date) < date - ? formatDay - : formatWeek - : timeYear(date) < date - ? formatMonth - : formatYear)(date); - }; } export const Daily = withOHLCData()(withSize({ style: { minHeight: 600 } })(withDeviceRatio()(Scales))); diff --git a/packages/stories/src/features/scales/index.stories.tsx b/packages/stories/src/features/scales/index.stories.tsx index d53cffcd2..408e2db11 100644 --- a/packages/stories/src/features/scales/index.stories.tsx +++ b/packages/stories/src/features/scales/index.stories.tsx @@ -1,4 +1,4 @@ -import { scaleLog } from "d3-scale"; +import { scaleLog, scaleUtc } from "d3-scale"; import * as React from "react"; import { Daily } from "./Scales"; @@ -8,4 +8,6 @@ export default { export const continuousScale = () => ; +export const utcScale = () => ; + export const logScale = () => ;