feat: add color support for line annotations
- Add color field to annotations schema with default blue (#3b82f6) - Add color picker UI with 5 preset colors (red, green, blue, yellow, white) - Pass selected color through component hierarchy (Page -> Toolbox, CandleChart -> SvgOverlay) - Store color when creating line annotations - Render lines with their stored color - Update database with color column - Preview lines show selected color during drawing Phase 1 of LINE_DRAWING_IMPROVEMENTS.md complete
This commit is contained in:
parent
5767669b2c
commit
006e95c266
6 changed files with 51 additions and 5 deletions
|
|
@ -26,7 +26,7 @@ export async function GET() {
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { timestamp, label_type, geometry } = body;
|
const { timestamp, label_type, geometry, color } = body;
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!timestamp || !label_type) {
|
if (!timestamp || !label_type) {
|
||||||
|
|
@ -45,6 +45,7 @@ export async function POST(request: NextRequest) {
|
||||||
timestamp,
|
timestamp,
|
||||||
label_type,
|
label_type,
|
||||||
geometry: geometryString,
|
geometry: geometryString,
|
||||||
|
color: color || '#3b82f6',
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import CandleChart, { CandleChartHandle } from '@/components/CandleChart';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [activeTool, setActiveTool] = useState<Tool>(null);
|
const [activeTool, setActiveTool] = useState<Tool>(null);
|
||||||
|
const [selectedColor, setSelectedColor] = useState('#3b82f6');
|
||||||
const chartRef = useRef<CandleChartHandle>(null);
|
const chartRef = useRef<CandleChartHandle>(null);
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
|
|
@ -37,6 +38,8 @@ export default function Home() {
|
||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
onToolChange={setActiveTool}
|
onToolChange={setActiveTool}
|
||||||
onExport={handleExport}
|
onExport={handleExport}
|
||||||
|
selectedColor={selectedColor}
|
||||||
|
onColorChange={setSelectedColor}
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|
@ -46,6 +49,7 @@ export default function Home() {
|
||||||
ref={chartRef}
|
ref={chartRef}
|
||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
onAnnotationChange={handleAnnotationChange}
|
onAnnotationChange={handleAnnotationChange}
|
||||||
|
selectedColor={selectedColor}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ interface Annotation {
|
||||||
interface CandleChartProps {
|
interface CandleChartProps {
|
||||||
activeTool: string | null;
|
activeTool: string | null;
|
||||||
onAnnotationChange?: () => void;
|
onAnnotationChange?: () => void;
|
||||||
|
selectedColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CandleChartHandle {
|
export interface CandleChartHandle {
|
||||||
|
|
@ -35,7 +36,7 @@ export interface CandleChartHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
||||||
({ activeTool, onAnnotationChange }, ref) => {
|
({ activeTool, onAnnotationChange, selectedColor }, ref) => {
|
||||||
const chartContainerRef = useRef<HTMLDivElement>(null);
|
const chartContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const chartRef = useRef<IChartApi | null>(null);
|
const chartRef = useRef<IChartApi | null>(null);
|
||||||
const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
|
const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
|
||||||
|
|
@ -277,6 +278,7 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
||||||
series={seriesRef.current}
|
series={seriesRef.current}
|
||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
onAnnotationChange={onAnnotationChange}
|
onAnnotationChange={onAnnotationChange}
|
||||||
|
selectedColor={selectedColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ interface Annotation {
|
||||||
endTime?: number;
|
endTime?: number;
|
||||||
endPrice?: number;
|
endPrice?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
color?: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ interface SvgOverlayProps {
|
||||||
series: ISeriesApi<'Candlestick'> | null;
|
series: ISeriesApi<'Candlestick'> | null;
|
||||||
activeTool: string | null;
|
activeTool: string | null;
|
||||||
onAnnotationChange?: () => void;
|
onAnnotationChange?: () => void;
|
||||||
|
selectedColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Point {
|
interface Point {
|
||||||
|
|
@ -33,6 +35,7 @@ export default function SvgOverlay({
|
||||||
series,
|
series,
|
||||||
activeTool,
|
activeTool,
|
||||||
onAnnotationChange,
|
onAnnotationChange,
|
||||||
|
selectedColor,
|
||||||
}: SvgOverlayProps) {
|
}: SvgOverlayProps) {
|
||||||
const svgRef = useRef<SVGSVGElement>(null);
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
|
@ -169,6 +172,7 @@ export default function SvgOverlay({
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
timestamp: drawingLine.start.time,
|
timestamp: drawingLine.start.time,
|
||||||
label_type: 'line',
|
label_type: 'line',
|
||||||
|
color: selectedColor,
|
||||||
geometry: {
|
geometry: {
|
||||||
startTime: drawingLine.start.time,
|
startTime: drawingLine.start.time,
|
||||||
startPrice: drawingLine.start.price,
|
startPrice: drawingLine.start.price,
|
||||||
|
|
@ -299,7 +303,7 @@ export default function SvgOverlay({
|
||||||
y1={start.y}
|
y1={start.y}
|
||||||
x2={end.x}
|
x2={end.x}
|
||||||
y2={end.y}
|
y2={end.y}
|
||||||
stroke="#3b82f6"
|
stroke={annotation.color || '#3b82f6'}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
style={{ cursor: activeTool === 'delete' ? 'pointer' : 'default' }}
|
style={{ cursor: activeTool === 'delete' ? 'pointer' : 'default' }}
|
||||||
/>
|
/>
|
||||||
|
|
@ -322,7 +326,7 @@ export default function SvgOverlay({
|
||||||
y1={start.y}
|
y1={start.y}
|
||||||
x2={end.x}
|
x2={end.x}
|
||||||
y2={end.y}
|
y2={end.y}
|
||||||
stroke="#3b82f6"
|
stroke={selectedColor}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeDasharray="5,5"
|
strokeDasharray="5,5"
|
||||||
opacity="0.6"
|
opacity="0.6"
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ interface ToolboxProps {
|
||||||
activeTool: Tool;
|
activeTool: Tool;
|
||||||
onToolChange: (tool: Tool) => void;
|
onToolChange: (tool: Tool) => void;
|
||||||
onExport: () => void;
|
onExport: () => void;
|
||||||
|
selectedColor: string;
|
||||||
|
onColorChange: (color: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Toolbox({ activeTool, onToolChange, onExport }: ToolboxProps) {
|
export default function Toolbox({ activeTool, onToolChange, onExport, selectedColor, onColorChange }: ToolboxProps) {
|
||||||
const handleToolClick = (tool: Tool) => {
|
const handleToolClick = (tool: Tool) => {
|
||||||
// Toggle: if clicking the active tool, deactivate it
|
// Toggle: if clicking the active tool, deactivate it
|
||||||
if (activeTool === tool) {
|
if (activeTool === tool) {
|
||||||
|
|
@ -54,6 +56,38 @@ export default function Toolbox({ activeTool, onToolChange, onExport }: ToolboxP
|
||||||
Draw Line
|
Draw Line
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* Color picker - shown when line tool is available */}
|
||||||
|
<div className="flex gap-1 px-1">
|
||||||
|
{[
|
||||||
|
{ color: '#ef4444', name: 'Red' },
|
||||||
|
{ color: '#22c55e', name: 'Green' },
|
||||||
|
{ color: '#3b82f6', name: 'Blue' },
|
||||||
|
{ color: '#eab308', name: 'Yellow' },
|
||||||
|
{ color: '#ffffff', name: 'White' },
|
||||||
|
].map((preset) => (
|
||||||
|
<Button
|
||||||
|
key={preset.color}
|
||||||
|
variant={selectedColor === preset.color ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
className="w-8 h-8 p-0"
|
||||||
|
style={{
|
||||||
|
backgroundColor: selectedColor === preset.color ? preset.color : 'transparent',
|
||||||
|
borderColor: preset.color,
|
||||||
|
borderWidth: '2px',
|
||||||
|
}}
|
||||||
|
onClick={() => onColorChange(preset.color)}
|
||||||
|
title={preset.name}
|
||||||
|
>
|
||||||
|
{selectedColor === preset.color ? '' : (
|
||||||
|
<div
|
||||||
|
className="w-4 h-4 rounded-sm"
|
||||||
|
style={{ backgroundColor: preset.color }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant={activeTool === 'delete' ? 'destructive' : 'outline'}
|
variant={activeTool === 'delete' ? 'destructive' : 'outline'}
|
||||||
className="justify-start gap-2"
|
className="justify-start gap-2"
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,6 @@ export const annotations = sqliteTable('annotations', {
|
||||||
timestamp: integer('timestamp').notNull(),
|
timestamp: integer('timestamp').notNull(),
|
||||||
label_type: text('label_type').notNull(),
|
label_type: text('label_type').notNull(),
|
||||||
geometry: text('geometry'), // JSON string for line coordinates
|
geometry: text('geometry'), // JSON string for line coordinates
|
||||||
|
color: text('color').default('#3b82f6'), // hex color code
|
||||||
created_at: integer('created_at').notNull(),
|
created_at: integer('created_at').notNull(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue