i'm using highcharts.js for the first time. seems great and powerful, i've enjoyed it thus far.
i'm trying, however, to get my column graph to look a bit sexier and am having trouble finding the information as to how i can do this, at least in regards to using a repeating background image for the column.
here's what i had designed:
You can add patterns by using this
color: {
pattern: 'https://rawgithub.com/highslide-software/pattern-fill/master/graphics/pattern3.png',
width: 6,
height: 6
}
Modify accordingly to suit your needs in seriesData
Demo - Fiddle Here
Just for the reference, I replied to the same question on Highcharts forum (http://highslide.com/forum/viewtopic.php?f=9&t=25673)
First of all, there was an idea of using pattern fill for columns. Basic demo was posted on Highcharts Uservoice site (http://highcharts.uservoice.com/forums/55896-general/suggestions/2378007-allow-fill-patterns-for-areas-columns-plot-bands).
You can improve that code to create SVG pattern with gradient fill. The main difficulty is support for data updates (and animations). Because of this we can't define fixed width for gradient element. See the sample code below:
Highcharts.Renderer.prototype.color = function (color, elem, prop) {
if (color && color.pattern && prop === 'fill') {
// SVG renderer
if (this.box.tagName == 'svg') {
var patternA,
patternB,
bgColor,
bgPattern,
image,
id;
id = 'highcharts-pattern-' + idCounter++;
patternA = this.createElement('pattern')
.attr({
id: id,
patternUnits: 'userSpaceOnUse',
width: '100%',
height: '100%'
})
.add(this.defs);
patternB = this.createElement('pattern')
.attr({
id: id + '-image',
patternUnits: 'userSpaceOnUse',
width: color.width,
height: color.width
})
.add(this.defs);
image = this.image(color.pattern, 0, 0, 6, 6)
.add(patternB);
bgColor = this.rect(0, 0, 0, 0, 0, 0)
.attr({
fill: color.fill,
width: '100%',
height: '100%'
})
.add(patternA);
bgPattern = this.rect(0, 0, 0, 0, 0, 0)
.attr({
fill: 'url(' + this.url + '#' + id + '-image)',
width: '100%',
height: '100%'
})
.add(patternA);
return 'url(' + this.url + '#' + id + ')';
// VML renderer
} else {
var markup = ['<', prop, ' type="tile" src="', color.pattern, '" />'];
elem.appendChild(
document.createElement(this.prepVML(markup)));
}
} else {
return base.apply(this, arguments);
}
};
Live demo available here: http://jsfiddle.net/Kr82z/
Related
So, I've been working with vue-konva and I have something like this:
<v-container>
<v-stage ref="stage">
<v-layer ref="baseImage">
<v-image>
</v-layer>
<v-layer ref="annotationLayer">
<v-rect ref="eventBox">
<v-rect ref="rubberBox">
<v-rect ref="annotationRect">
</v-layer>
</v-stage>
<v-container>
Currently there are some issues if I want to draw new boxes, when there are other annotationRects already on the image. Because they are technically above the eventBox and rubberbox, they are "blocking" these two layers when the cursor is above an existing annotationRect.
But, I don't want to just constantly have eventBox and rubberBox be on top of annotationRect because I need to be able to interact with annotationRect to move them, resize them ,etc.
Is there a way for me to reorder eventBox, rubberBox, and annotationRect, i.e. changing the order to (bottom to top) annotationRect-eventBox-rubberBox from the original eventBox-rubberBox-annotationRect and back, on the fly, for example when the vue component receives an event from another component?
You need to define your eventBox, rubberBox, and annotationRect inside order array in the state of your app. Then you can use v-for directive to render items from the array:
<template>
<div>
<v-stage ref="stage" :config="stageSize" #click="changeOrder">
<v-layer>
<v-text :config="{text: 'Click me to change order', fontSize: 15}"/>
<v-rect v-for="item in items" v-bind:key="item.id" :config="item"/>
</v-layer>
<v-layer ref="dragLayer"></v-layer>
</v-stage>
</div>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
items: [
{ id: 1, x: 10, y: 50, width: 100, height: 100, fill: "red" },
{ id: 2, x: 50, y: 70, width: 100, height: 100, fill: "blue" }
]
};
},
methods: {
changeOrder() {
const first = this.items[0];
// remove first item:
this.items.splice(0, 1);
// add it to the top:
this.items.push(first);
}
}
};
</script>
DEmo: https://codesandbox.io/s/vue-konva-list-render-l70vs?file=/src/App.vue
I'm drawing a closed line over a busy image as a background. It's working fine.
But my goal is to highlight the stroke as much as possible. Therefore, I'm looking for a way to make the line's stroke color 'negative/inverted' pixel of what's underneath that line.
For example:
<Line ... stroke="inverted" />
Possible? Achievable in some other way?
You may be able to use the globalCompositionOperation.
Mozilla docs here
Konva example here - see the parameters for the Konva.Rect() call. The example is for text, so not exactly as you require but hopefully you can adapt for the line requirement.
I tried to create a snippet to illustrate this but could not get exactly what you require. However, you can see how to apply the globalCompositionOperation parameter for a Konva line. Change the value of the comp variable to the other composition mode names from the Mozilla page to see their effects. The line is draggable.
An alternative may be to get the canvas pixel data, work out the pixels under the line and invert them individually.
// This is the color-composition mode
var comp = 'exclusion'
var stage = new Konva.Stage({
container: 'container',
width: 500,
height: 230
});
var layer = new Konva.Layer();
stage.add(layer)
var rect = new Konva.Rect({ x: 10, y: 0, width:500, height: 230,
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 250, y: 150 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow']
})
layer.add(rect)
var ln = new Konva.Line({
points: [25, 70, 300, 20],
stroke: 'red',
strokeWidth: 15,
lineCap: 'round',
lineJoin: 'round',
draggable: true,
globalCompositeOperation: comp
});
layer.add(ln);
layer.draw()
stage.draw()
#container {
width: 500px;
height: 230px;
background-color: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/3.2.5/konva.js"></script>
<div id="container"></div>
I would like to build a chart like this
enter image description here
Drag the red point or two blue lines on the left chart, then the chart will generate the second one on the right.
Please advice me how to do with highcharts or other chart library. Thank you very much!
There is no such feature in Highcharts, and implementation will be not so easy, but I'm not saying it's not possible. In order to achieve the similar effect, you can use renderer feature, generate the circle and lines. Then change their attributes (position and dimensions), when dragging the circle. For example:
chart: {
events: {
load() {
var chart = this,
circleWidth = 5,
circleInitPos = {
x: 100,
y: 100
},
lineWidth = 2,
indicator = chart.renderer.g('indicator').attr('zIndex', 5).add(),
leftLine = chart.renderer.rect(0, circleInitPos.y, circleInitPos.x, lineWidth)
.attr({
fill: 'blue'
})
.add(indicator),
bottomLine = chart.renderer.rect(circleInitPos.x, circleInitPos.y, lineWidth, chart.containerHeight - circleInitPos.y)
.attr({
fill: 'blue'
})
.add(indicator),
circle = chart.renderer.circle(100, 100, circleWidth)
.attr({
fill: 'red'
}).add(indicator);
circle.drag = false;
chart.container.onmousemove = function(e) {
if (circle.drag) {
let normalizedEvent = chart.pointer.normalize(e),
groupPos = indicator.getBBox(),
leftLineLen = chart.plotWidth - e.chartX,
bottomLineLen = chart.plotHeight - e.chartY;
// Recalculate dimensions
leftLine.attr({
width: e.chartX,
y: e.chartY
})
bottomLine.attr({
height: chart.containerHeight - e.chartY,
y: e.chartY,
x: e.chartX
})
circle.attr({
x: e.chartX,
y: e.chartY
})
}
}
circle.element.onmousedown = function() {
circle.drag = true
}
circle.element.onmouseup = function() {
circle.drag = false
}
}
}
},
I prepared the example which shows how to do it, so here it is: https://jsfiddle.net/rgs4v5az/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer
i'm using Highcharts to create a pie, and I need to put user's profile image in a circle in the middle of it. I have managed to add the image but couldn't get it to be round, and I managed to add a circle but without image background :/
what it the best way to combine the tow?
by the way it must be part of the svg and not an absolute div on top of the pie, because the tooltip needs to be on top of that image with opacity
to add a circle I used this code :
var pixelX = 438;
var pixelY = 276;
var pixelR = 70;
// add my circle
chart.renderer.circle(pixelX, pixelY, pixelR)
.attr({
zIndex: 100,
align: 'center',
// fill: 'url(https://lh6.googleusercontent.com/-gaAgFzRLxQQ/AAAAAAAAAAI/AAAAAAAAAjc/ies0iU4BEqU/photo.jpg)',
color: 'url(https://lh6.googleusercontent.com/-gaAgFzRLxQQ/AAAAAAAAAAI/AAAAAAAAAjc/ies0iU4BEqU/photo.jpg)',
stroke: 'black',
'stroke-width': 2
})
.css({
backgroundImage :'url(https://lh6.googleusercontent.com/-gaAgFzRLxQQ/AAAAAAAAAAI/AAAAAAAAAjc/ies0iU4BEqU/photo.jpg)'
})
.add();
});
this example shows how to do it with pure SVG : http://jsfiddle.net/9zkfodwp/1/
resolved it by defining a pattern with the img, works fine but now need to find a way for the pattern to be no-repeat.
code used to add pattern:
function(chart) { // on complete
var r = chart.renderer,
pattern = r.createElement('pattern')
.attr({
id: 'pattern',
patternUnits: 'userSpaceOnUse',
x: 0,
y: 0,
width: 180,
height: 190,
viewBox: '0 0 135 135'
})
.add(r.defs);
r.rect(0, 0, 135, 135, 0)
.attr('fill', '#ddd')
.add(pattern);
r.image(profileImg,0,0,135,135)
.add(pattern);
});
and when I add the circle it can have a fill of the pattern:
// add my circle
this.circle = chart.renderer.circle(pixelX, pixelY, pixelR).attr({
fill: 'url(#pattern)'
});
this.circle.add();
I am developing on iOS 9.2 SDK & Titannium SDK v5.1.2.GA.
In my iPad app; there is a product tab page, which has a "Discount" button. When you click it, a Popover with a TextField and Picker is shown like this:
The above is created on the fly. (not using a controller + view).
This works as intended. I wanted to extend this a little further by recording the given discount to a product in alloy.js in a global array variable called Alloy.Globals.ProductDiscounts = []; (so it can be used later).
The way I "capture" the new discount price is by listening to the "hide" event on the picker. Then update the global array.
For debugging purpose, I added a console log to make sure it's getting recorded correctly and then in the Appcelerator Studio console window, I started see this endless output like this:
I had to kill the simulator to stop this weird constant output of nulls.
This is my code so far, any idea why the console window is spazzing out? Also, why isn't my global array isn't getting set? or is it getting set, but I missed the actual console.log entry?
// Subscribe to line discount button click event
lineDiscountButton.addEventListener('click', function(e)
{
// Stop further events
e.cancelBubble = true;
// Create popover
var discountPopover = Titanium.UI.iPad.createPopover({
arrowDirection: Titanium.UI.iPad.POPOVER_ARROW_DIRECTION_RIGHT,
orignalPrice: e.source.orignalPrice,
priceButton: e.source.priceButton
});
var discountPopoverView = Titanium.UI.createView({
width: 250,
height: 210
});
// Create discount popover view wrapper
var discountPopoverViewWrapper = Titanium.UI.createView({
top: 10,
left: 10,
right: 10,
bottom: 10,
layout: 'vertical'
});
discountPopoverViewWrapper.add(Titanium.UI.createLabel({
top: 0,
left: 0,
color: '#5C5C5C',
font: {
fontSize: 12
},
text: 'Enter a new Price'
}));
discountPopoverViewWrapper.add(Titanium.UI.createView({
top: 0,
height: 1,
backgroundColor: '#0088CE',
width: '100%'
}));
var discountPrice = Titanium.UI.createTextField({
top: 0,
width: '100%',
height: Titanium.UI.SIZE,
hintText: discountPopover.orignalPrice,
value: discountPopover.orignalPrice,
backgroundColor: '#FFFFFF',
font: {
fontSize: 18,
fontWeight: 'bold'
},
color: '#5C5C5C'
});
discountPopoverViewWrapper.add(discountPrice);
discountPopoverViewWrapper.add(Titanium.UI.createLabel({
top: 10,
left: 0,
color: '#5C5C5C',
font: {
fontSize: 12
},
text: 'Or Select a Discount Percent'
}));
discountPopoverViewWrapper.add(Titanium.UI.createView({
top: 0,
height: 1,
backgroundColor: '#0088CE',
width: '100%'
}));
var discountPercentPicker = Titanium.UI.createPicker({
top: 0,
width: Titanium.UI.FILL,
height: 112
});
var discountPercentValues = [];
for (var i = 0; i <= 100; i++) {
discountPercentValues.push(Titanium.UI.createPickerRow({
title: i +'%'
}));
}
discountPercentPicker.add(discountPercentValues);
discountPercentPicker.addEventListener('change', function(e) {
if (parseInt(e.rowIndex) === 0) {
discountPrice.value = discountPopover.orignalPrice;
} else {
discountPrice.value = (discountPopover.orignalPrice - (discountPopover.orignalPrice * (parseInt(e.rowIndex) / 100))).toFixed(2);
}
});
discountPopoverViewWrapper.add(discountPercentPicker);
// Add discount popover view wrapper to view
discountPopoverView.add(discountPopoverViewWrapper);
// Set popover content view
discountPopover.contentView = discountPopoverView;
// Subscribe to popover hide event
discountPopover.addEventListener('hide', function(e) {
e.cancelBubble = true;
Alloy.Globals.ProductDiscounts[discountPopover.priceButton.sku] = parseFloat(discountPrice.value).toFixed(2);
Alloy.Globals.LiveBasketCollection.executeQuery("UPDATE live_basket SET Price = "+ discountPrice.value +" WHERE Sku = '"+ discountPopover.priceButton.sku +"'");
discountPopover.priceButton.price = Alloy.Globals.DeviceDefaults.CurrencySymbol + discountPrice.value;
discountPopover.priceButton.title = (discountPopover.priceButton.basketQuantity > 0 ? discountPopover.priceButton.basketQuantity +' x ' : '') + discountPopover.priceButton.price;
Titanium.App.fireEvent('redrawBasket');
discountPopover = discountPopoverView = discountPrice = discountPercentPicker = discountPercentValues = null;
console.log(Alloy.Globals.ProductDiscounts);
});
// Show popover
discountPopover.show({
view: lineDiscountButton,
animated: true
});
});
Just as a Question... Why are you cancelling bubbling.
without more of the code base I can only try and make a suggestion.
1) addeventlistener.
lineDiscountButton.addEventListener('click', setupData);
2) setupData
Function setupData(e) {
lineDiscountButton.removeEventListener('click', setupData);
Your code from the inline function.
}
Basically remove the event listener once activated, so add and remove as required.
I don't want to teach you to suck eggs, but to remove an event listener, you have to use exactly the same parameters as adding it. Thus inline function on event listeners are not ideal, although they work separating the code out into its own function is preferable.
Next Alloy globals.... Not good practice. I am guessing you want to have the data only for the duration of the running of the app, and not for future use.
If you need it for future use, you can store the data in properties.
Hope this helps
T.