diff --git a/src/plugins/graph/adornments/movable-line/movable-line-model.ts b/src/plugins/graph/adornments/movable-line/movable-line-model.ts index 9279264a7a..f5ffbd1cb7 100644 --- a/src/plugins/graph/adornments/movable-line/movable-line-model.ts +++ b/src/plugins/graph/adornments/movable-line/movable-line-model.ts @@ -8,8 +8,12 @@ import { kMovableLineType } from "./movable-line-types"; import { IGraphModel } from "../../models/graph-model"; import { IClueObject } from "../../../../models/annotations/clue-object"; -export function getAnnotationId(lineKey: string, position: string) { - return `movable_line_handle:${lineKey}:${position}`; +export function getAnnotationId(lineKey: string, type: "handle"|"equation", position?: "lower"|"upper") { + if (position) { + return `movable_line_${type}:${lineKey}:${position}`; + } else { + return `movable_line_${type}:${lineKey}`; + } } export const MovableLineInstance = types.model("MovableLineInstance", { @@ -123,8 +127,9 @@ export const MovableLineModel = AdornmentModel const objects: IClueObject[] = []; if (self.isVisible) { for (const key of self.lines.keys()) { - objects.push({ tileId, objectType: "movable-line-handle", objectId: getAnnotationId(key, "lower") }); - objects.push({ tileId, objectType: "movable-line-handle", objectId: getAnnotationId(key, "upper") }); + objects.push({ tileId, objectType: "movable-line-handle", objectId: getAnnotationId(key, "handle", "lower") }); + objects.push({ tileId, objectType: "movable-line-handle", objectId: getAnnotationId(key, "handle", "upper") }); + objects.push({ tileId, objectType: "movable-line-equation", objectId: getAnnotationId(key, "equation") }); } } return objects; diff --git a/src/plugins/graph/adornments/movable-line/movable-line.tsx b/src/plugins/graph/adornments/movable-line/movable-line.tsx index 5ead1e7897..776347c4d8 100644 --- a/src/plugins/graph/adornments/movable-line/movable-line.tsx +++ b/src/plugins/graph/adornments/movable-line/movable-line.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useContext, useEffect, useRef, useState} from "react"; import {autorun} from "mobx"; import { observer } from "mobx-react-lite"; -import {drag, select} from "d3"; +import { drag, select, Selection} from "d3"; import {useAxisLayoutContext} from "../../imports/components/axis/models/axis-layout-context"; import {ScaleNumericBaseType} from "../../imports/components/axis/axis-types"; import {INumericAxisModel} from "../../imports/components/axis/models/axis-model"; @@ -84,6 +84,14 @@ export const MovableLine = observer(function MovableLine(props: IProps) { } }, [kHandle1Loc, kHandle2Loc]); + const positionEquation = useCallback((elt: Selection, point: Point) => { + elt.style('left', `${point.x}px`) + .style('top', `${point.y}px`); + if (model.isVisible) { + annotationLocationSetter?.set(getAnnotationId(instanceKey, "equation"), point); + } + }, [annotationLocationSetter, instanceKey, model]); + // Refresh the line useEffect(function refresh() { const disposer = autorun(() => { @@ -134,7 +142,7 @@ export const MovableLine = observer(function MovableLine(props: IProps) { elt .attr('cx', x) .attr('cy', y); - const annotationId = getAnnotationId(instanceKey, index===1 ? "lower" : "upper"); + const annotationId = getAnnotationId(instanceKey, "handle", index===1 ? "lower" : "upper"); if (model.isVisible) { annotationLocationSetter?.set(annotationId, { x, y }); } else { @@ -146,22 +154,26 @@ export const MovableLine = observer(function MovableLine(props: IProps) { function refreshEquation() { if (!pointsOnAxes.current) return; const - screenX = xScale((pointsOnAxes.current.pt1.x + pointsOnAxes.current.pt2.x) / 2) / xSubAxesCount, - screenY = yScale((pointsOnAxes.current.pt1.y + pointsOnAxes.current.pt2.y) / 2) / ySubAxesCount, attrNames = {x: xAttrName, y: yAttrName}, string = equationString(slope, intercept, attrNames), - equation = select(equationContainerSelector).select('p'); + equation = select(equationContainerSelector).select('p'); select(equationContainerSelector) .style('width', `${plotWidth}px`) .style('height', `${plotHeight}px`); equation.html(string); - // The equation may have been unpinned from the line if the user - // dragged it away from the line. Only move the equation if it - // is still pinned. - if (!lineModel?.currentEquationCoords) { - equation.style('left', `${screenX}px`) - .style('top', `${screenY}px`); + + const fixedCoords = lineModel?.currentEquationCoords; + if (fixedCoords) { + // The equation is unpinned -- the user dragged it away from the line. Use stored position. + // It is stored in the model as a fraction of the graph height & width. + positionEquation(equation, { x: fixedCoords.x*plotWidth, y: fixedCoords.y*plotHeight }); + } else { + // Pinned to line; calculate position. + const + screenX = xScale((pointsOnAxes.current.pt1.x + pointsOnAxes.current.pt2.x) / 2) / xSubAxesCount, + screenY = yScale((pointsOnAxes.current.pt1.y + pointsOnAxes.current.pt2.y) / 2) / ySubAxesCount; + positionEquation(equation, { x: screenX, y: screenY }); } } @@ -187,7 +199,7 @@ export const MovableLine = observer(function MovableLine(props: IProps) { }); return () => disposer(); }, [instanceId, layout, pointsOnAxes, lineObject, plotHeight, plotWidth, xScale, yScale, model, model.lines, - xAttrName, xSubAxesCount, xAxis, yAttrName, ySubAxesCount, yAxis, xRange, yRange, + xAttrName, xSubAxesCount, xAxis, yAttrName, ySubAxesCount, yAxis, xRange, yRange, positionEquation, equationContainerSelector, subPlotKey, instanceKey, calculateHandlePosition, annotationLocationSetter] ); @@ -293,7 +305,7 @@ export const MovableLine = observer(function MovableLine(props: IProps) { moveEquation = useCallback((event: { x: number, y: number, dx: number, dy: number }) => { if (event.dx !== 0 || event.dy !== 0) { - const equation = select(`${equationContainerSelector} p`), + const equation = select(`${equationContainerSelector} p`), equationNode = equation.node() as Element, equationWidth = equationNode?.getBoundingClientRect().width || 0, equationHeight = equationNode?.getBoundingClientRect().height || 0, @@ -304,11 +316,10 @@ export const MovableLine = observer(function MovableLine(props: IProps) { x = left / plotWidth, y = top / plotHeight; + positionEquation(equation, { x: left, y: top }); model.dragEquation({x, y}, instanceKey); - equation.style('left', `${left}px`) - .style('top', `${top}px`); } - }, [equationContainerSelector, instanceKey, model, plotWidth, plotHeight]), + }, [equationContainerSelector, plotWidth, plotHeight, positionEquation, model, instanceKey]), endMoveEquation = useCallback(() => { model.saveEquationCoords(instanceKey); @@ -373,21 +384,11 @@ export const MovableLine = observer(function MovableLine(props: IProps) { .style('width', `${plotWidth}px`) .style('height', `${plotHeight}px`); - const equationP = equationDiv - .append('p') + equationDiv + .append('p') .attr('class', 'movable-line-equation') .attr('data-testid', `movable-line-equation-${model.classNameFromKey(subPlotKey)}`); - // If the equation is not pinned to the line, set its initial coordinates to - // the values specified in the model. - const equationCoords = model.lines?.get(instanceKey)?.currentEquationCoords; - if (equationCoords) { - const left = equationCoords.x * 100, - top = equationCoords.y * 100; - equationP.style('left', `${left}%`) - .style('top', `${top}%`); - } - newLineObject.equation = equationDiv; setLineObject(newLineObject);