CSS Scaling of img in React changes position offset behavior - css

I am building a component that will display any image inside a parent div and allows dragging of the image (when larger than the div) as well as scaling on both a double click or pinch on mobile. I am using inline style changes on the image to test the behavior and so far everything works as I wanted...except that when I change the image transform:scale() all the calculations that effectively set the correct limits to prevent offsetting an image outside the parent div no longer behave as expected. It does work perfectly if I keep the scale=1.
So for example, with a parent Div width and height of 500/500 respectively, and an image that say is 1500x1000, I prevent any "over"offsetting when dragging the image by setting limits of leftLimit = -(1500-500) = -1000 and topLimit = -(1000-500) = -500. This works perfectly at the initial scale of 1. However, I have a dblClick event that scales the image upwards at .5 intervals, and once the scale changes from 1 to any other number, the methods I use above to calculate offset Limits are no longer value. So for example if I increase the scale to 2, in theory that same 1500x1000 image in a 500x500 div should NOW have leftLimit = -(3000-500) = -2500 and topLimit = -(2000-500) = 1500. But these new calculated limits allow the image to be dragged right out of the parent div region. For reference here is the code. Any help or methods for testing what's actually going on would be very much appreciated.
Note the image is being loaded as a file for test, it's a fairly large base64 string. The code is as follows (btw, I am figuring my use of so many 'state' variables probably exposes my ignorance of how such values could/should really persist across renderings. I am still quite new to React) :
import * as React from 'react'
import * as types from '../../types/rpg-types'
import imgSource from './testwebmap.jpg'
import * as vars from '../../data/mapImage'
const testImg = require('./testwebmap.jpg')
export default function MyMapImage() {
let divRef = React.useRef<HTMLDivElement>(null)
let imgRef = React.useRef<HTMLImageElement>(null)
const [loaded, setLoaded] = React.useState<boolean>(false)
const [imgTop, setImgTop] = React.useState<number>(0)
const [imgLeft, setImgLeft] = React.useState<number>(0)
const [scHeight, setSCHeight] = React.useState<number>(100)
const [scWidth, setSCWidth] = React.useState<number>(100)
const [imgScale, setImgScale] = React.useState<number>(1)
const [natHeight, setNatHeight] = React.useState<number>(100)
const [natWidth, setNatWidth] = React.useState<number>(100)
const [oldXCoord, setOldXCoord] = React.useState<number>(0)
const [oldYCoord, setOldYCoord] = React.useState<number>(0)
const [topLimit, setTopLimit] = React.useState<number>(0)
const [leftLimit, setLeftLimit] = React.useState<number>(0)
const [isScaling, setIsScaling] = React.useState<boolean>(false)
const [isDragging, setIsDragging] = React.useState<boolean>(false)
const [isFirstPress, setIsFirstPress] = React.useState<boolean>(false)
const [accel, setAccel] = React.useState<number>(1)
const [touchDist, setTouchDist] = React.useState<number>(0)
const [cfg, setCfg] = React.useState<types.ImageConfig>({
img: '',
imgTOP: 0,
imgLEFT: 0,
offsetX: 0,
offsetY: 0,
isFirstPress: true,
isDragging: false,
isScaling: false,
divHeight: 500,
divWidth: 500,
topLimit: 0,
leftLimit: 0,
isLoaded: true,
oldMouseX: 0,
oldMouseY: 0,
touchDist: 0,
})
const setNewImageLimits = () => {
const img = imgRef
let heightLimit: number
let widthLimit: number
console.log(`imgScale is: ${imgScale}`)
//console.log(`current offsets: ${imgLeft}:${imgTop}`)
console.log(`img width/Height: ${img.current?.width}:${img.current?.height}`)
console.log(img)
img.current
? (heightLimit = Math.floor(imgScale * img.current.naturalHeight - cfg.divHeight))
: (heightLimit = 0)
img.current
? (widthLimit = Math.floor(imgScale * img.current.naturalWidth - cfg.divWidth))
: (widthLimit = 0)
setTopLimit(-heightLimit)
setLeftLimit(-widthLimit)
setImgLeft(0)
setImgTop(0)
console.log(
'New Image limits set with topLimit:' + heightLimit + ' and leftLimit:' + widthLimit
)
}
const handleImageLoad = () => {
if (imgRef) {
const img = imgRef
//console.log(imgRef)
let heightLimit: number
let widthLimit: number
img.current ? (heightLimit = img.current.naturalHeight - cfg.divHeight) : (heightLimit = 0)
img.current ? (widthLimit = img.current.naturalWidth - cfg.divWidth) : (widthLimit = 0)
setTopLimit(-heightLimit)
setLeftLimit(-widthLimit)
setNatHeight(img.current ? img.current.naturalHeight : 0)
setNatWidth(img.current ? img.current.naturalWidth : 0)
setSCHeight(img.current ? img.current.naturalHeight : 0)
setSCWidth(img.current ? img.current.naturalWidth : 0)
console.log('Image Loaded with topLimit:' + heightLimit + ' and leftLimit:' + widthLimit)
}
}
React.useEffect(() => {
if (imgRef.current?.complete) {
handleImageLoad()
}
}, [])
React.useEffect(() => {
setNewImageLimits()
console.log(`imgScale is: ${imgScale}`)
console.log('Image has with topLimit:' + topLimit + ' and leftLimit:' + leftLimit)
}, [imgScale])
function distance(e: any) {
let zw = e.touches[0].pageX - e.touches[1].pageX
let zh = e.touches[0].pageY - e.touches[1].pageY
if (zw * zw + zh * zh != 0) {
return Math.sqrt(zw * zw + zh * zh)
} else return 0
}
function setCoordinates(e: any) {
let canMouseX: number
let canMouseY: number
if (e?.nativeEvent?.clientX && e?.nativeEvent?.clientY) {
//console.log(e)
//canMouseX = parseInt(e.clientX - cfg.offsetX)
canMouseX = e.nativeEvent.clientX - cfg.offsetX
canMouseY = e.nativeEvent.clientY - cfg.offsetY
//console.log(`${canMouseX}:${canMouseY}`)
} else if (e?.nativeEvent?.targetTouches) {
canMouseX = e.nativeEvent.targetTouches.item(0)?.clientX - cfg.offsetX
canMouseY = e.nativeEvent.targetTouches.item(0)?.clientY - cfg.offsetY
// This isn't doing anything (noticeable)
// e.preventDefault();
} else return {}
return {
canMouseX,
canMouseY,
}
}
const handleMouseUp = (e: any) => {
let { canMouseX, canMouseY } = setCoordinates(e)
setIsScaling(false)
setIsDragging(false)
setIsFirstPress(true)
setAccel(1)
console.log('Mouse UP Event function')
}
const handleMouseDown = (e: any) => {
const { canMouseX, canMouseY } = setCoordinates(e)
//console.log('Mouse DOWN Event function')
e.preventDefault()
//console.log(`Mouse Down ${canMouseX}:${canMouseY}`)
canMouseX ? setOldXCoord(canMouseX) : setOldXCoord(0)
canMouseY ? setOldYCoord(canMouseY) : setOldYCoord(0)
setIsDragging(true)
setCfg({ ...cfg, isDragging: true })
if (e?.targetTouches) {
e.preventDefault()
if (e?.nativeEvent?.touches?.length > 1) {
// detected a pinch
setTouchDist(distance(e))
setCfg({ ...cfg, touchDist: distance(e), isScaling: true })
setIsScaling(true)
setIsDragging(false)
} else {
// set the drag flag
setIsScaling(false)
setIsDragging(true)
}
}
setIsFirstPress(false)
setCfg({ ...cfg, isFirstPress: true })
}
const handleDoubleClick = (e: any) => {
const { canMouseX, canMouseY } = setCoordinates(e)
if (imgScale === 3) {
setImgScale(1)
} else {
setImgScale(imgScale + 0.5)
}
}
const handleMouseMove = (e: any) => {
let scaling = isScaling
let dragging = isDragging
let tempImgScale: number = 1
const { canMouseX, canMouseY } = setCoordinates(e)
let yDiff: number
let xDiff: number
let newLeft: number
let newTop: number
if (e.targetTouches) {
e.preventDefault()
if (e.touches.length > 1) {
//detected a pinch
setIsScaling(true)
setIsDragging(false)
scaling = true
} else {
setIsScaling(false)
setIsDragging(true)
}
}
//console.log(`isScaling : ${isScaling}`)
if (scaling) {
//...adding rndScaleTest to force processing of scaling randomly
let dist = distance(e)
//Can't divide by zero, so return dist in denom. if touchDist still at initial 0 value
tempImgScale = dist / (touchDist === 0 ? dist : touchDist)
//console.log(`imgScale is: ${imgScale}`)
if (tempImgScale < 1) tempImgScale = 1 //for now no scaling down allowed...
if (tempImgScale > 2) tempImgScale = 2 //...and scaling up limited to 2.5x
setSCHeight(Math.floor(imgScale * natHeight))
setSCWidth(Math.floor(imgScale * natWidth))
setImgScale(tempImgScale)
setTouchDist(dist)
}
// if the drag flag is set, clear the canvas and draw the image
if (isDragging) {
yDiff = canMouseY && oldYCoord ? accel * (canMouseY - oldYCoord) : 0
xDiff = canMouseX && oldXCoord ? accel * (canMouseX - oldXCoord) : 0
if (imgLeft + xDiff <= leftLimit) {
setImgLeft(leftLimit)
} else if (imgLeft + xDiff >= 0) {
setImgLeft(0)
} else setImgLeft(imgLeft + xDiff)
if (imgTop + yDiff <= topLimit) {
setImgTop(topLimit)
} else if (imgTop + yDiff >= 0) {
setImgTop(0)
} else setImgTop(imgTop + yDiff)
if (accel < 4) {
setAccel(accel + 1)
}
}
//console.log('Mouse **MOVE Event function')
setOldXCoord(canMouseX || 0)
setOldYCoord(canMouseY || 0)
}
const handleMouseLeave = (e: any) => {
setIsScaling(false)
setIsDragging(false)
setIsFirstPress(true)
setAccel(1)
console.log('Mouse LEAVE Event function')
}
return (
<div>
<div className="portrait">
<div
ref={divRef}
className="wrapper"
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onTouchEnd={handleMouseUp}
onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown}
onTouchMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onDoubleClick={handleDoubleClick}
>
<img
ref={imgRef}
src={`data:image/jpeg;base64,${vars.bigImage}`}
style={{
transform: `scale(${imgScale})`,
transformOrigin: `top left`,
objectPosition: `${imgLeft}px ${imgTop}px`,
}}
onLoad={handleImageLoad}
/>
</div>
</div>
<span>{`imgLeft: ${imgLeft}px `}</span>
<span>{`imgTop: ${imgTop}px `}</span>
</div>
)
}

In the end, the problem does lie with the use of Scale(), and I could not rely on it for purposes of tracking position and offset. So I ended up explicitly scaling 'height' and 'width' for the img and keeping the scaling value as a reference when required to calculate new values for these. The parent Div and Img essentially now look like this:
<div
ref={divRef}
className="wrapper"
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onTouchEnd={handleMouseUp}
onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown}
onTouchMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onDoubleClick={handleDoubleClick}
>
<img
ref={imgRef}
src={`data:image/jpeg;base64,${vars.bigImage}`}
style={{
transform: `translate(${imgLeft}px, ${imgTop}px)`,
height: `${scHeight}px`,
width: `${scWidth}px)`,
transformOrigin: `top left`,
}}
onLoad={handleImageLoad}
/>
</div>
This works fine, calculated offsets match the expected values, and translation of the image within the parent div behaves as expected.

Related

Get axis value on mouse hover

how do we get X axis value ?
If you see above image , how do we get X axis value if mouse moved over the chart or clicked on anywhere of charts , is there any callback functions ?
You can attach an event handler to chart.onChartBackgroundMouseMove event.
Then you will need to convert the event location to an engine coordinate space. This can be done with publicEngine.engineLocation2Client() method, chart.engine.clientLocation2Engine(ev.clientX, ev.clientY)
The last the is to convert the engine location to a point on a scale. This can be done with translatePoint() function. const onScale = translatePoint(engineLocation, chart.engine.scale, {x: chart.getDefaultAxisX().scale, y: chart.getDefaultAxisY().scale})
chartOHLC.onChartBackgroundMouseMove((obj, ev)=>{
const point = obj.engine.clientLocation2Engine(ev.clientX, ev.clientY)
const onScale = translatePoint(point, obj.engine.scale, {x:obj.getDefaultAxisX().scale, y:obj.getDefaultAxisY().scale})
console.log(onScale)
})
See the snippet below where I log the mouse location on a scale to console.
// Extract required parts from LightningChartJS.
const {
lightningChart,
AxisTickStrategies,
LegendBoxBuilders,
SolidFill,
SolidLine,
emptyLine,
ColorRGBA,
UIOrigins,
Themes,
translatePoint
} = lcjs
// Import data-generator from 'xydata'-library.
const {
createOHLCGenerator,
createProgressiveTraceGenerator
} = xydata
// Create dashboard to house two charts
const db = lightningChart().Dashboard({
// theme: Themes.dark
numberOfRows: 2,
numberOfColumns: 1
})
// Decide on an origin for DateTime axis.
const dateOrigin = new Date(2018, 0, 1)
// Chart that contains the OHLC candle stick series and Bollinger band
const chartOHLC = db.createChartXY({
columnIndex: 0,
rowIndex: 0,
columnSpan: 1,
rowSpan: 1
})
// Use DateTime TickStrategy with custom date origin for X Axis.
chartOHLC
.getDefaultAxisX()
.setTickStrategy(
AxisTickStrategies.DateTime,
(tickStrategy) => tickStrategy.setDateOrigin(dateOrigin)
)
// Modify Chart.
chartOHLC
.setTitle('Trading dashboard')
//Style AutoCursor.
.setAutoCursor(cursor => {
cursor.disposeTickMarkerY()
cursor.setGridStrokeYStyle(emptyLine)
})
.setPadding({
right: 40
})
chartOHLC.onChartBackgroundMouseMove((obj, ev) => {
const point = obj.engine.clientLocation2Engine(ev.clientX, ev.clientY)
const onScale = translatePoint(point, obj.engine.scale, {
x: obj.getDefaultAxisX().scale,
y: obj.getDefaultAxisY().scale
})
console.log(onScale)
})
// The top chart should have 66% of view height allocated to it. By giving the first row a height of 2, the relative
// height of the row becomes 2/3 of the whole view (default value for row height / column width is 1)
db.setRowHeight(0, 2)
// Create a LegendBox for Candle-Stick and Bollinger Band
const legendBoxOHLC = chartOHLC.addLegendBox(LegendBoxBuilders.HorizontalLegendBox)
.setPosition({
x: 100,
y: 100
})
.setOrigin(UIOrigins.RightTop)
// Define function which sets Y axis intervals nicely.
let setViewNicely
// Create OHLC Figures and Area-range.
//#region
// Get Y-axis for series (view is set manually).
const stockAxisY = chartOHLC.getDefaultAxisY()
.setScrollStrategy(undefined)
.setTitle('USD')
// Add series.
const areaRangeFill = new SolidFill().setColor(ColorRGBA(100, 149, 237, 50))
const areaRangeStroke = new SolidLine()
.setFillStyle(new SolidFill().setColor(ColorRGBA(100, 149, 237)))
.setThickness(1)
const areaRange = chartOHLC.addAreaRangeSeries({
yAxis: stockAxisY
})
.setName('Bollinger band')
.setHighFillStyle(areaRangeFill)
.setLowFillStyle(areaRangeFill)
.setHighStrokeStyle(areaRangeStroke)
.setLowStrokeStyle(areaRangeStroke)
.setMouseInteractions(false)
.setCursorEnabled(false)
const stockFigureWidth = 5.0
const borderBlack = new SolidLine().setFillStyle(new SolidFill().setColor(ColorRGBA(0, 0, 0))).setThickness(1.0)
const fillBrightRed = new SolidFill().setColor(ColorRGBA(255, 0, 0))
const fillDimRed = new SolidFill().setColor(ColorRGBA(128, 0, 0))
const fillBrightGreen = new SolidFill().setColor(ColorRGBA(0, 255, 0))
const fillDimGreen = new SolidFill().setColor(ColorRGBA(0, 128, 0))
const stock = chartOHLC.addOHLCSeries({
yAxis: stockAxisY
})
.setName('Candle-Sticks')
// Setting width of figures
.setFigureWidth(stockFigureWidth)
// Styling positive candlestick
.setPositiveStyle(candlestick => candlestick
// Candlestick body fill style
.setBodyFillStyle(fillBrightGreen)
// Candlestick body fill style
.setBodyStrokeStyle(borderBlack)
// Candlestick stroke style
.setStrokeStyle((strokeStyle) => strokeStyle.setFillStyle(fillDimGreen))
)
.setNegativeStyle(candlestick => candlestick
.setBodyFillStyle(fillBrightRed)
.setBodyStrokeStyle(borderBlack)
.setStrokeStyle((strokeStyle) => strokeStyle.setFillStyle(fillDimRed))
)
// Make function that handles adding OHLC segments to series.
const add = (ohlc) => {
// ohlc is equal to [x, open, high, low, close]
stock.add(ohlc)
// AreaRange looks better if it extends a bit further than the actual OHLC values.
const areaOffset = 0.2
areaRange.add({
position: ohlc[0],
high: ohlc[2] - areaOffset,
low: ohlc[3] + areaOffset,
})
}
// Generate some static data.
createOHLCGenerator()
.setNumberOfPoints(100)
.setDataFrequency(24 * 60 * 60 * 1000)
.generate()
.toPromise()
.then(data => {
data.forEach(add)
setViewNicely()
})
//#endregion
// Create volume.
//#region
const chartVolume = db.createChartXY({
columnIndex: 0,
rowIndex: 1,
columnSpan: 1,
rowSpan: 1
})
// Use DateTime TickStrategy with custom date origin for X Axis.
chartVolume
.getDefaultAxisX()
.setTickStrategy(
AxisTickStrategies.DateTime,
(tickStrategy) => tickStrategy.setDateOrigin(dateOrigin)
)
// Modify Chart.
chartVolume
.setTitle('Volume')
.setPadding({
right: 40
})
// Create a LegendBox as part of the chart.
const legendBoxVolume = chartVolume.addLegendBox(LegendBoxBuilders.HorizontalLegendBox)
.setPosition({
x: 100,
y: 100
})
.setOrigin(UIOrigins.RightTop)
// Create Y-axis for series (view is set manually).
const volumeAxisY = chartVolume.getDefaultAxisY()
.setTitle('USD')
// Modify TickStyle to hide gridstrokes.
.setTickStrategy(
// Base TickStrategy that should be styled.
AxisTickStrategies.Numeric,
// Modify the the tickStyles through a mutator.
(tickStrat) => tickStrat
// Modify the Major tickStyle to not render the grid strokes.
.setMajorTickStyle(
tickStyle => tickStyle.setGridStrokeStyle(emptyLine)
)
// Modify the Minor tickStyle to not render the grid strokes.
.setMinorTickStyle(
tickStyle => tickStyle.setGridStrokeStyle(emptyLine)
)
)
const volumeFillStyle = new SolidFill().setColor(ColorRGBA(0, 128, 128, 60))
const volumeStrokeStyle = new SolidLine()
.setFillStyle(volumeFillStyle.setA(255))
.setThickness(1)
const volume = chartVolume.addAreaSeries({
yAxis: volumeAxisY
})
.setName('Volume')
.setFillStyle(volumeFillStyle)
.setStrokeStyle(volumeStrokeStyle)
createProgressiveTraceGenerator()
.setNumberOfPoints(990)
.generate()
.toPromise()
.then(data => {
volume.add(data.map(point => ({
x: point.x * 2.4 * 60 * 60 * 1000,
y: Math.abs(point.y) * 10
})))
setViewNicely()
})
//#endregion
// Add series to LegendBox and style entries.
const entries1 = legendBoxOHLC.add(chartOHLC)
entries1[0]
.setButtonOnFillStyle(areaRangeStroke.getFillStyle())
.setButtonOnStrokeStyle(emptyLine)
const entries2 = legendBoxVolume.add(chartVolume)
entries2[0]
.setButtonOnFillStyle(volumeStrokeStyle.getFillStyle())
.setButtonOnStrokeStyle(emptyLine)
setViewNicely = () => {
const yBoundsStock = {
min: areaRange.getYMin(),
max: areaRange.getYMax(),
range: areaRange.getYMax() - areaRange.getYMin()
}
const yBoundsVolume = {
min: volume.getYMin(),
max: volume.getYMax(),
range: volume.getYMax() - volume.getYMin()
}
// Set Y axis intervals so that series don't overlap and volume is under stocks.
volumeAxisY.setInterval(yBoundsVolume.min, yBoundsVolume.max)
stockAxisY.setInterval(yBoundsStock.min - yBoundsStock.range * .33, yBoundsStock.max)
}
stock.setResultTableFormatter((builder, series, segment) => {
return builder
.addRow(series.getName())
.addRow(series.axisX.formatValue(segment.getPosition()))
.addRow('Open ' + segment.getOpen().toFixed(2))
.addRow('High ' + segment.getHigh().toFixed(2))
.addRow('Low ' + segment.getLow().toFixed(2))
.addRow('Close ' + segment.getClose().toFixed(2))
})
volume.setResultTableFormatter((builder, series, position, high, low) => {
return builder
.addRow(series.getName())
.addRow(series.axisX.formatValue(position))
.addRow('Value ' + Math.round(high))
.addRow('Base ' + Math.round(low))
})
<script src="https://unpkg.com/#arction/xydata#1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#2.2.1/dist/lcjs.iife.js"></script>

ZigZag path onMouseDrag with paper.js

Hi im tryign to create a zigzag path using Path.js's onMouseDrag function but getting in to a bit of a muddle here is a sketch
and code
var path
var zigzag
var length
var count
var delta=[]
tool.fixedDistance= 20
function onMouseDown(event){
path= new Path()
path.add(event.point)
zigzag= new Path()
}
function onMouseDrag(event){
event.delta += 90
path.add(event.delta)
delta.push(event.delta)
}
function onMouseUp(event){
length= path.segments.length
zigzag= new Path()
zigzag.add(event.point)
console.log(delta)
delta.forEach(( zig , i) => {
zigzag.add(i % 2 == 0 ? zig + 20 : zig - 20)
})
zigzag.selected= true
}
Based on my previous answer, here is a sketch demonstrating a possible way to do it.
let line;
let zigZag;
function onMouseDown(event) {
line = new Path({
segments: [event.point, event.point],
strokeColor: 'black'
});
zigZag = createZigZagFromLine(line);
}
function onMouseDrag(event) {
line.lastSegment.point = event.point;
if (zigZag) {
zigZag.remove();
}
zigZag = createZigZagFromLine(line);
}
function createZigZagFromLine(line) {
const zigZag = new Path({ selected: true });
const count = 20, length = line.length;
for (let i = 0; i <= count; i++) {
const offset = i / count * length;
const normal = i === 0 || i === count
? new Point(0, 0)
: line.getNormalAt(offset) * 30;
const point = line.getPointAt(offset).add(i % 2 == 0 ? normal
: -normal);
zigZag.add(point);
}
return zigZag;
}

scale konva image from the center and hold position on redraw

Hey guys I have a slight problem here I have a Konva.js app which is working pretty well. I have this function called SET_STAGE_DATA that is supposed to save the images current position on the canvas so it can be redrawn when the canvas disapears. It works but when you scale the image from the left the image shifts. I am not sure how to fix this. I thought I may be able to fix it with offset but to no avail.
Heres my SET_STAGE_DATA function
[SET_STAGE_DATA](state){
const isStage = state.stage;
if(isStage){
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];;
let i = g.findOne("Image").getAttrs();
let position = g.findOne("Image").position();
console.log("this is position", position);
let group = { x: g.getX(), y: g.getY() };
let image = { x: i.width, y: i.height, position };
let obj = { image, group };
stageData.push(obj);
}
I use the stageData just before I build the image like so
let groupPos;
let imagePos;
if(state.stageData){
console.log(true);
for(let i =0; i<state.stageData.length; i++){
imageObj.width = state.stageData[i].image.x;
imageObj.height = state.stageData[i].image.y;
imagePos = state.stageData[i].position;
groupPos = state.stageData[i].group;
}
} else {
groupPos = {
x: state.stage.width() / 2 - imageObj.width / 2,
y: state.stage.height() / 2 - imageObj.height / 2
};
}
console.log("data", {groupPos, image: {width: imageObj.width, height: imageObj.height}});
const image = new Konva.Image({
image: imageObj,
width: imageObj.width,
height: imageObj.height,
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
if(imagePos){
image.position(imagePos)
}
const group = new Konva.Group({
draggable: true,
x: groupPos.x,
y: groupPos.y
});
I also have the typical update function found in the documentation:
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
// image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
state.layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function(evt) {
group.draggable(true);
commit(SET_STAGE_DATA);
dispatch(UPDATE_ANIMATION, state.selectedNode);
state.layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
document.body.style.cursor = "pointer";
this.strokeWidth(4);
state.layer.draw();
});
anchor.on("mouseout", function() {
document.body.style.cursor = "default";
this.strokeWidth(2);
state.layer.draw();
});
group.add(anchor);
}
state.layer.draw();
},
when I scale to the left the image goes outside of the anchors on redraw. I know I can fix this visually as long as I don't redraw the image by doing image.position(topLeft.position()); as you can see is commented out but it always acts as if positioning isn't set if you drag from the left. If I scale from the bottom right everything is fine. it acts as though it goes back to the previous position of the images left top corrner but from what I understand using stage.find() will give you the current stage. I'm at a totally loss.
See in the image how it does not stay in the box. Any help here would be great thanks.
[1]: https://i.stack.imgur.com/HEfHP.jpg
Update the SET_STAGE_DATA function to look like this
[SET_STAGE_DATA](state){
const isStage = state.stage;
const isEditorMode = state.editorMode;
if(isStage && !isEditorMode){
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];
let i = g.findOne("Image").getAttrs();
let group = {x: g.getX() + i.x, y: g.getY() + i.y };
let image = i;
let obj = { image, group };
stageData.push(obj);
}
state.stageData = stageData;
} else {
state.stageData = null;
}
}
group.x and y don't update on scale but image.x and y do. On move image x and y are 0 and if you add them to the group.x and y it will put the anchors where you need them to be.

LiipImagineBundle / Retina

Is it possible to somehow configure bundle to generate images also for retina display, like #2x?
Or can someone give me an advice how to deal with retina?
Thanks
According to this comment by nahakiole, there are 2 solutions for this:
You can either use the picture element which would provide the syntax
to declare multiple sources for an image.
http://w3c.github.io/html/semantics-embedded-content.html#the-picture-element
The other method which we've tried was, if you can guarantee that the
image exists, to use a modified version of the retina.js which adds
_retina to the filter name and checks if a image with this name exists.
Modified version of retina.js by nahakiole:
/*-----------------------------------------------------------------------------------*/
/* RETINA.JS
/*-----------------------------------------------------------------------------------*/
(function () {
var regex = /(media\/cache\/filter_[A-Z]+)/i //Added this
function t(e) {
this.path = e;
var t = this.path.split("."),
n = t.slice(0, t.length - 1).join("."),
r = t[t.length - 1];
this.at_2x_path = (n + '.' + r).replace(regex, '$1_retina') //Changed that
}
function n(e) {
this.el = e, this.path = new t(this.el.getAttribute("src"));
var n = this;
this.path.check_2x_variant(function (e) {
e && n.swap()
})
}
var e = typeof exports == "undefined" ? window : exports;
e.RetinaImagePath = t, t.confirmed_paths = [], t.prototype.is_external = function () {
return !!this.path.match(/^https?\:/i) && !this.path.match("//" + document.domain)
}, t.prototype.check_2x_variant = function (e) {
var n, r = this;
if (this.is_external()) return e(!1);
if (this.at_2x_path in t.confirmed_paths) return e(!0);
n = new XMLHttpRequest, n.open("HEAD", this.at_2x_path), n.onreadystatechange = function () {
return n.readyState != 4 ? e(!1) : n.status >= 200 && n.status <= 399 ? (t.confirmed_paths.push(r.at_2x_path), e(!0)) : e(!1)
}, n.send()
}, e.RetinaImage = n, n.prototype.swap = function (e) {
function n() {
t.el.complete ? (t.el.setAttribute("width", t.el.offsetWidth), t.el.setAttribute("height", t.el.offsetHeight), t.el.setAttribute("src", e)) : setTimeout(n, 5)
}
typeof e == "undefined" && (e = this.path.at_2x_path);
var t = this;
n()
}, e.devicePixelRatio > 1 && (window.onload = function () {
var e = document.getElementsByTagName("img"),
t = [],
r, i;
for (r = 0; r < e.length; r++) i = e[r], t.push(new n(i))
})
})();

How can I draw dotted line in flex

After some search and reading the Graphics class document, I can't find a way to specify the line style of a line. I mean whether the line is a solid one or a dotted one. Could anybody help me?
Thanks!
You can't, well, not just by using Flex library classes anyway. You can do it yourself though, of course. Here's a class that implements it (modified from code found here, thanks Ken Fox):
/*Copyright (c) 2006 Adobe Systems Incorporated
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
package com.some.package {
import mx.graphics.IStroke;
import flash.display.Graphics;
public class GraphicsUtils {
public static function _drawDashedLine( target:Graphics, strokes:Array, pattern:Array,
drawingState:DashStruct,
x0:Number, y0:Number, x1:Number, y1:Number):void {
var dX:Number = x1 - x0;
var dY:Number = y1 - y0;
var len:Number = Math.sqrt( dX * dX + dY * dY );
dX /= len;
dY /= len;
var tMax:Number = len;
var t:Number = -drawingState.offset;
var patternIndex:int = drawingState.patternIndex;
var strokeIndex:int = drawingState.strokeIndex;
var styleInited:Boolean = drawingState.styleInited;
while( t < tMax ) {
t += pattern[patternIndex];
if( t < 0 ) {
var x:int = 5;
}
if( t >= tMax ) {
drawingState.offset = pattern[patternIndex] - ( t - tMax );
drawingState.patternIndex = patternIndex;
drawingState.strokeIndex = strokeIndex;
drawingState.styleInited = true;
t = tMax;
}
if( styleInited == false ) {
(strokes[strokeIndex] as IStroke).apply( target );
}
else {
styleInited = false;
}
target.lineTo( x0 + t * dX, y0 + t * dY );
patternIndex = ( patternIndex + 1 ) % pattern.length;
strokeIndex = ( strokeIndex + 1 ) % strokes.length;
}
}
public static function drawDashedLine( target:Graphics, strokes:Array, pattern:Array,
x0:Number, y0:Number, x1:Number, y1:Number ):void {
target.moveTo( x0, y0 );
var struct:DashStruct = new DashStruct();
_drawDashedLine( target, strokes, pattern, struct, x0, y0, x1, y1 );
}
public static function drawDashedPolyLine( target:Graphics, strokes:Array, pattern:Array,
points:Array ):void {
if( points.length == 0 ) { return; }
var prev:Object = points[0];
var struct:DashStruct = new DashStruct();
target.moveTo( prev.x, prev.y );
for( var i:int = 1; i < points.length; i++ ) {
var current:Object = points[i];
_drawDashedLine( target, strokes, pattern, struct,
prev.x, prev.y, current.x, current.y );
prev = current;
}
}
}
}
class DashStruct {
public function init():void {
patternIndex = 0;
strokeIndex = 0;
offset = 0;
}
public var patternIndex:int = 0;
public var strokeIndex:int = 0;
public var offset:Number = 0;
public var styleInited:Boolean = false;
}
And you can use it like:
var points:Array = [];
points.push( { x: 0, y: 0 } );
points.push( { x: this.width, y: 0 } );
points.push( { x: this.width, y: this.height } );
points.push( { x: 0, y: this.height } );
points.push( { x: 0, y: 0 } );
var strokes:Array = [];
var transparentStroke:Stroke = new Stroke( 0x0, 0, 0 );
strokes.push( new Stroke( 0x999999, 2, 1, false, 'normal', CapsStyle.NONE ) );
strokes.push( transparentStroke );
strokes.push( new Stroke( 0x222222, 2, 1, false, 'normal', CapsStyle.NONE ) );
strokes.push( transparentStroke );
GraphicsUtils.drawDashedPolyLine( this.graphics, strokes, [ 16, 5, 16, 5 ], points );
Well there is no Dashed or Dotted line in flex.
However, you can create your own line or border:
http://cookbooks.adobe.com/post_Creating_a_dashed_line_custom_border_with_dash_and-13286.html
Try and enjoy!
I ran across this, today, which I like a lot. Provides for dashed/dotted lines and a few other things (curves, primarily).
http://flexdevtips.blogspot.com/2010/01/drawing-dashed-lines-and-cubic-curves.html
I wrote some simple DashedLine code -
import flash.display.Graphics;
import spark.primitives.Line;
public class DashedLine extends Line
{
public var dashLength:Number = 10;
override protected function draw(g:Graphics):void
{
// Our bounding box is (x1, y1, x2, y2)
var x1:Number = measuredX + drawX;
var y1:Number = measuredY + drawY;
var x2:Number = measuredX + drawX + width;
var y2:Number = measuredY + drawY + height;
var isGap:Boolean;
// Which way should we draw the line?
if ((xFrom <= xTo) == (yFrom <= yTo))
{
// top-left to bottom-right
g.moveTo(x1, y1);
x1 += dashLength;
for(x1; x1 < x2; x1 += dashLength){
if(isGap){
g.moveTo(x1, y1);
} else {
g.lineTo(x1, y1);
}
isGap = !isGap;
}
}
else
{
// bottom-left to top-right
g.moveTo(x1, y2);
x1 += dashLength;
for(x1; x1 < x2; y2 += dashLength){
if(isGap){
g.moveTo(x1, y2);
} else {
g.lineTo(x1, y2);
}
isGap = !isGap;
}
}
}
}
Then use it like this -
<comps:DashedLine top="{}" left="0" width="110%" >
<comps:stroke>
<s:SolidColorStroke color="0xff0000" weight="1"/>
</comps:stroke>
</comps:DashedLine>
I do it pretty simple:
public static function drawDottedLine(target:Graphics, xFrom:Number, yFrom:Number, xTo:Number, yTo:Number, dotLenth:Number = 10):void{
var isGap:Boolean;
target.moveTo(xFrom, yFrom);
xFrom += dotLenth;
for(xFrom; xFrom < xTo; xFrom += dotLenth){
if(isGap){
target.moveTo(xFrom, yFrom);
} else {
target.lineTo(xFrom, yFrom);
}
isGap = !isGap;
}
}
UPD: :) The same way as in the link provided by Ross.

Resources