diff --git a/src/container/chart_template.ts b/src/container/chart_template.ts index 8a23a057..33368491 100644 --- a/src/container/chart_template.ts +++ b/src/container/chart_template.ts @@ -27,6 +27,7 @@ import { DataAxisExpression } from "../core/prototypes/marks/data_axis.attrs"; import { MappingType, ScaleMapping, ValueMapping } from "../core/specification"; import { Region2DSublayoutOptions } from "../core/prototypes/plot_segments/region_2d/base"; import { GuideAttributeNames } from "../core/prototypes/guides"; +import { scaleLinear } from "d3-scale"; export interface TemplateInstance { chart: Specification.Chart; @@ -477,13 +478,25 @@ export class ChartTemplate { const scale = new Scale.LinearScale(); scale.inferParameters(vector); if (inference.autoDomainMin) { - axisDataBinding.domainMin = scale.domainMin; axisDataBinding.dataDomainMin = scale.domainMin; } if (inference.autoDomainMax) { - axisDataBinding.domainMax = scale.domainMax; axisDataBinding.dataDomainMax = scale.domainMax; } + if (axisDataBinding.allowScrolling) { + const scrollScale = scaleLinear() + .domain([0, 100]) + .range([ + axisDataBinding.dataDomainMin, + axisDataBinding.dataDomainMax, + ]); + const start = scrollScale(axisDataBinding.scrollPosition); + axisDataBinding.domainMin = start; + axisDataBinding.domainMax = start + axisDataBinding.windowSize; + } else { + axisDataBinding.domainMin = scale.domainMin; + axisDataBinding.domainMax = scale.domainMax; + } if (axis.defineCategories) { axisDataBinding.categories = defineCategories(vector); } diff --git a/src/core/prototypes/plot_segments/axis.ts b/src/core/prototypes/plot_segments/axis.ts index f8e742b5..501aa4f2 100644 --- a/src/core/prototypes/plot_segments/axis.ts +++ b/src/core/prototypes/plot_segments/axis.ts @@ -117,7 +117,10 @@ export class AxisRenderer { this.setStyle(data.style); this.oppositeSide = data.side == "opposite"; this.scrollRequired = data.allowScrolling; - this.shiftAxis = data.barOffset == null || data.barOffset === 0; + this.shiftAxis = + (data.barOffset == null || data.barOffset === 0) && + ((data.allCategories && data.windowSize < data.allCategories.length) || + Math.abs(data.dataDomainMax - data.dataDomainMin) > data.windowSize); switch (data.type) { case "numerical": { @@ -1462,57 +1465,6 @@ export function buildAxisWidgets( ] ) ); - widgets.push( - manager.sectionHeader(strings.objects.dataAxis.scrolling) - ); - widgets.push( - manager.inputBoolean( - { - property: axisProperty, - field: "allowScrolling", - }, - { - type: "checkbox", - label: strings.objects.dataAxis.allowScrolling, - observerConfig: { - isObserver: true, - properties: { - property: axisProperty, - field: "windowSize", - }, - value: 10, - }, - } - ) - ); - if (data.allowScrolling) { - widgets.push( - manager.inputNumber( - { - property: axisProperty, - field: "windowSize", - }, - { - maximum: 1000, - minimum: 1, - label: strings.objects.dataAxis.windowSize, - } - ) - ); - widgets.push( - manager.inputNumber( - { - property: axisProperty, - field: "barOffset", - }, - { - maximum: 1000, - minimum: -1000000, - label: strings.objects.dataAxis.barOffset, - } - ) - ); - } widgets.push(makeAppearance()); } break; diff --git a/src/core/prototypes/plot_segments/region_2d/cartesian.ts b/src/core/prototypes/plot_segments/region_2d/cartesian.ts index 8ac559c8..53cabdd1 100644 --- a/src/core/prototypes/plot_segments/region_2d/cartesian.ts +++ b/src/core/prototypes/plot_segments/region_2d/cartesian.ts @@ -416,8 +416,16 @@ export class CartesianPlotSegment extends PlotSegmentClass< const attrs = this.state.attributes; const props = this.object.properties; const g = []; - // TODO optimize axis render; - if (props.xData && props.xData.visible && props.xData.allowScrolling) { + + if ( + props.xData && + props.xData.visible && + props.xData.allowScrolling && + ((props.xData.allCategories && + props.xData.allCategories.length > props.xData.windowSize) || + Math.abs(props.xData.dataDomainMax - props.xData.dataDomainMin) > + props.xData.windowSize) + ) { const axisRenderer = new AxisRenderer().setAxisDataBinding( props.xData, 0, @@ -441,12 +449,12 @@ export class CartesianPlotSegment extends PlotSegmentClass< if (!props.xData.allCategories) { return; } - props.xData.scrollPosition = position; + props.xData.scrollPosition = 100 - position; const start = Math.floor( ((props.xData.allCategories.length - props.xData.windowSize) / 100) * - position + props.xData.scrollPosition ); props.xData.categories = props.xData.allCategories.slice( start, @@ -454,13 +462,14 @@ export class CartesianPlotSegment extends PlotSegmentClass< ); if (props.xData.categories.length === 0) { - props.xData.allCategories - .reverse() - .slice(start - 1, start + props.xData.windowSize); + props.xData.allCategories.slice( + start - 1, + start + props.xData.windowSize + ); } } else if (props.xData.type === AxisDataBindingType.Numerical) { const scale = scaleLinear() - .domain([0, 100]) + .domain([100, 0]) .range([props.xData.dataDomainMin, props.xData.dataDomainMax]); props.xData.scrollPosition = position; const start = scale(position); @@ -478,8 +487,10 @@ export class CartesianPlotSegment extends PlotSegmentClass< props.yData && props.yData.visible && props.yData.allowScrolling && - props.yData.allCategories && - props.yData.allCategories.length > props.yData.windowSize + ((props.yData.allCategories && + props.yData.allCategories.length > props.yData.windowSize) || + Math.abs(props.yData.dataDomainMax - props.yData.dataDomainMin) > + props.yData.windowSize) ) { const axisRenderer = new AxisRenderer().setAxisDataBinding( props.yData, @@ -510,19 +521,20 @@ export class CartesianPlotSegment extends PlotSegmentClass< 100) * position ); - props.yData.categories = props.yData.allCategories - .reverse() - .slice(start, start + props.yData.windowSize) - .reverse(); + props.yData.categories = props.yData.allCategories.slice( + start, + start + props.yData.windowSize + ); if (props.yData.categories.length === 0) { - props.yData.allCategories - .reverse() - .slice(start - 1, start + props.yData.windowSize); + props.yData.allCategories.slice( + start - 1, + start + props.yData.windowSize + ); } } else if (props.yData.type === AxisDataBindingType.Numerical) { const scale = scaleLinear() - .domain([0, 100]) + .domain([100, 0]) .range([props.yData.dataDomainMin, props.yData.dataDomainMax]); props.yData.scrollPosition = position; const start = scale(position); diff --git a/src/core/prototypes/plot_segments/virtualScroll.tsx b/src/core/prototypes/plot_segments/virtualScroll.tsx index 25c5a4ca..8a564911 100644 --- a/src/core/prototypes/plot_segments/virtualScroll.tsx +++ b/src/core/prototypes/plot_segments/virtualScroll.tsx @@ -48,7 +48,8 @@ export const VirtualScrollBar: React.FC = ({ handlePosition = 0; } - handlePosition = ((trackSize - handleSize) / 100) * handlePosition; // map % to axis position + handlePosition = + ((trackSize - handleSize) / 100) * (100 - handlePosition); // map % to axis position if (vertical) { handlePositionY = handlePosition; @@ -113,10 +114,10 @@ export const VirtualScrollBar: React.FC = ({ let newPosition = position; if (vertical) { const trackSize = Math.abs(trackElement.bottom - trackElement.top); - newPosition = 100 - (deltaY / trackSize) * 100; + newPosition = (deltaY / trackSize) * 100; } else { const trackSize = Math.abs(trackElement.right - trackElement.left); - newPosition = (deltaX / trackSize) * 100; + newPosition = 100 - (deltaX / trackSize) * 100; } if (newPosition > 100) { @@ -134,7 +135,14 @@ export const VirtualScrollBar: React.FC = ({ const onClick = React.useCallback( (sign: number) => { - const newPosition = position + sign * 5; + let newPosition = position + sign * 5; + + if (newPosition > 100) { + newPosition = 100; + } + if (newPosition < 0) { + newPosition = 0; + } setPosition(newPosition); onScroll(newPosition); }, @@ -195,7 +203,7 @@ export const VirtualScrollBar: React.FC = ({ fill: "#b3b0ad", }} onClick={() => { - onClick(-1); + onClick(1); }} /> = ({ fill: "#b3b0ad", }} onClick={() => { - onClick(1); + onClick(-1); }} /> = ({ fill: "#b3b0ad", }} onClick={() => { - onClick(-1); + onClick(1); }} /> = ({ fill: "#b3b0ad", }} onClick={() => { - onClick(1); + onClick(-1); }} />