I'm trying to recreate this chart in Altair but can't figure out how to make a few minor edits: https://datawrapper.dwcdn.net/E0jaK/1/
Here's my current progress: https://imgur.com/RMuTU7h
The three edits I'm trying to figure out are:
How to left-align y-axis labels
How to add spacing between the labels and the y-axis
How to move bar number labels to the base of the bars instead of the ends
Any insight on how to implement these edits would be great.
Here's the raw data:
Crime,Count
Larceny/Theft,739
Assault,177
Burglary/Breaking & Entering,133
Destruction of Property/Vandalism,128
Drugs,107
Motor Vehicle Theft,95
Fraud,71
Sex Offenses,57
Suspicious Activity,45
Trespassing,23
Family Offenses,22
Weapons Violations,21
This is the theme I used:
def chart_theme():
font = "Calibri"
return {
"width": 700,
"height": 300,
"background": "white",
"config": {
"title": {
"fontSize": 20,
"anchor": "start"
},
"axisY": {
"labelFont": font,
"labelFontSize": 13,
"labelLimit":200,
"ticks": False,
"titleFont": font,
"titleFontSize": 12,
"titleAlign":"right",
"titleAngle": 0,
"titleY": -10,
"titleX": 25,
},
"view":{"stroke": "transparent",
},
}
}
And here's the chart code:
base = alt.Chart(df, title='San Francisco Crime (11/05 - 11/11)').encode(
x=alt.X('Count', axis=None),
y=alt.Y('Crime', title='Crimes reported to SFPD, by top complaint',
sort=list(df.Crime.values)))
bars = base.mark_bar(size=22).encode(color=alt.condition(
alt.datum.Count > 700,
alt.value('#e17700'),
alt.value('#00648e')))
text = base.mark_text(
color='white',
align='right',
size=12,
dx=-3
).encode(
text='Count')
chart = bars + text
chart
Any guidance/suggestions would be greatly appreciated.
How about something like this
import pandas as pd
import altair as alt
data = {'Larceny/Theft':739,
'Assault':177,
'Burglary/Breaking & Entering':133,
'Destruction of Property/Vandalism':128,
'Drugs':107,
'Motor Vehicle Theft':95,
'Fraud':71,
'Sex Offenses':57,
'Suspicious Activity':45,
'Trespassing':23,
'Family Offenses':22,
'Weapons Violations':21}
df = pd.DataFrame(list(data.items()), columns=['Crime', 'Count'])
base = alt.Chart(df, title='San Francisco Crime (11/05 - 11/11)').encode(
x=alt.X('Count', axis=None),
y=alt.Y('Crime', title='Crimes reported to SFPD, by top complaint',
sort=list(df.Crime.values)))
bars = base.mark_bar(size=22).encode(color=alt.condition(
alt.datum.Count > 700,
alt.value('#e17700'),
alt.value('#00648e')))
text = alt.Chart(df).mark_text(
color='white',
align='left',
x=3
).encode(alt.Text('Count:N'), alt.Y('Crime', sort=list(df.Crime.values)))
chart = bars + text
chart.properties(width=700).configure_axisY(
titleAngle=0,
titleY=-10,
titleX=-60,
labelPadding=160,
labelAlign='left'
)
Related
I'm trying to achieve a spinning circle with artwork as a mask.
From what I've seen there is no way to use a moving watermark or an automatic rotation of an image. Is it possible with transloadit?
The result should be a "vinyl" spinning.
This question is quite complex to answer, but it's very much do-able with Transloadit. I'll be using python to answer it primarily for OpenCV, but of course you can use a language that you prefer - I'll try and make it as language agnostic as possible.
I used 4 different templates to get the result we want, as well as some local python magic to tie everything together - here goes.
First we need to resize the image so it fits nicely onto the vinyl record.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"resize": {
"use": ":original",
"robot": "/image/resize",
"format": "png",
"resize_strategy": "fillcrop",
"width": 350,
"height": 350,
"imagemagick_stack": "v2.0.7"
}
}
}
Now, we want to mask this image using OpenCV and NumPy like so:
# Mask the image
# Reads the input image
img = cv2.imread(img_path)
# Creates a mask with the same size as the image
mask = np.zeros(img.shape, dtype=np.uint8)
# Creates a white circle in the centre
mask = cv2.circle(mask, (175, 175), 175, (255, 255, 255), -1)
# Makes a small whole in the centre of the mask
mask = cv2.circle(mask, (175, 175), 20, (0, 0, 0), -1)
result = cv2.bitwise_and(img, mask)
This will take an image, and create a mask for it that should look like a donut.
Then, using a bitwise and operation between the image and the mask you end up with a cookie cutter of the original image
Yet we still need to remove the black background - which is what we use this template for:
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"trimmed": {
"use": ":original",
"robot": "/image/resize",
"alpha": "Activate",
"type": "TrueColor",
"transparent": "0,0,0",
"imagemagick_stack": "v2.0.7"
}
}
}
This will just make all black pixels transparent.
We can now use Transloadit's watermark feature to overlay this image onto our vinyl record
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"watermark": {
"use": ":original",
"robot": "/image/resize",
"watermark_size": "33%",
"watermark_position": "center",
"imagemagick_stack": "v2.0.7"
}
}
}
Now, all that is left is to make it spin. What we can do is create say 60 frames, and have the image rotate, then using the /video/merge robot - to stitch it all together into a seamless GIF.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"rotate_image": {
"use": ":original",
"robot": "/image/resize",
"rotation": "${file.basename}",
"resize_strategy": "crop",
"imagemagick_stack": "v2.0.7"
},
"animated": {
"robot": "/video/merge",
"use": {
"steps": [
{
"name": "rotate_image",
"as": "image"
}
],
"bundle_steps": true
},
"result": true,
"ffmpeg_stack": "v4.3.1",
"ffmpeg": {
"f": "gif",
"pix_fmt": "rgb24"
}
}
}
}
Here I use the image's name to determine how many degrees to rotate it by - so when I'm uploading the files to the robot I will name the image based on its index in an array using this python code:
# Now we make a list of images that represent each frame
no_of_frames = 60
assembly = tl.new_assembly({'template_id': [SPINNING_VINYL_TEMPLATE_ID]})
directory = 'Assets/Frames/{image}'.format(image=img_name)
# Length of our animation in seconds
length = 2
for i in range(no_of_frames):
if not os.path.exists(directory):
os.mkdir(directory)
# Creates an image based on the index in the animation
# We pass this to the robot so it knows how many degrees to rotate the image by
location = '{directory}/{index}.png'.format(directory=directory, index=round(i*360/no_of_frames))
cv2.imwrite(location, finished_vinyl)
assembly.add_file(open(location, 'rb'))
This is my end result
I'm trying to move label test to right few pixels because the way it's displayed now it looks like it is more to the left:
Label text is aligned to center for 2d bar charts but when you have 3d bars you have this slight offset effect to left that needs to be corrected.Label position values are: "bottom", "top", "right", "left", "inside", "middle".
I wasn't able to fine tune it.
Any ideas on this?
As mentioned in my comment, the labels are centered with respect to the angle setting for 3D charts. The API doesn't allow you to shift the label left or right, so you have to manipulate the graph SVG nodes directly through the drawn event. If you set addClassNames to true, you can retrieve the label elements using document.querySelectorAll through the generated DOM class names and then modifying the translate value in the transform attribute accordingly. You can use a technique from this SO answer to easily manipulate the transform attribute as an object:
// ...
"addClassNames": true,
"listeners": [{
"event": "drawn",
"method": function(e) {
Array.prototype.forEach.call(
document.querySelectorAll(".amcharts-graph-g4 .amcharts-graph-label"),
function(graphLabel) {
var transform = parseTransform(graphLabel.getAttribute('transform'));
transform.translate[0] = parseFloat(transform.translate[0]) + 5; //adjust X offset
graphLabel.setAttribute('transform', serializeTransform(transform));
});
}
}]
// ...
// from http://stackoverflow.com/questions/17824145/parse-svg-transform-attribute-with-javascript
function parseTransform(a) {
var b = {};
for (var i in a = a.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?)+\))+/g)) {
var c = a[i].match(/[\w\.\-]+/g);
b[c.shift()] = c;
}
return b;
}
//serialize transform object back to an attribute string
function serializeTransform(transformObj) {
var transformStrings = [];
for (var attr in transformObj) {
transformStrings.push(attr + '(' + transformObj[attr].join(',') + ')');
}
return transformStrings.join(',');
}
Updated fiddle
Been stumped on this for a little bit.
I found some other help on zoomToIndexes, but I cant get the zoomToDates to work on my page.
Live page is
b2 resource urq sales
Im trying to set the initial view to show from 2000 to current.. I want to slap some original sales data from early 80's in the graph, but dont want the graph to initially show the last 30+ years..
Any help would be MUCH appreciated!
zoomToDates takes real JavaScript Date objects as parameters:
chart.zoomToDates(new Date(2005, 0, 1), new Date(2015, 11, 31));
You can use chart's rendered event to "pre-zoom" on load as well:
var chart = AmCharts.makeChart("chartdiv", {
// your chart config
// ...
});
chart.addListener("rendered", function(event) {
event.chart.zoomToDates(new Date(2005, 0, 1), new Date(2015, 11, 31));
});
Note, that months in Date() constructor parameter (second parameter) are zero-based. Meaning January is 0, February - 1, etc.
You should use valueAxis property of chart object for zoomToValues. I hope this might help you.
var chart= AmCharts.makeChart("chartdiv", {
"type": "gantt",
"theme": "black",
...
});
zoomChart();
chart.addListener("dataUpdated", zoomChart);
function zoomChart(event) {
chart.valueAxis.zoomToValues(new Date(2017, 2, 10), new Date(2017,2,12));
// or ==> event.chart.valueAxis.zoomToValues(new Date(2017, 2, 10), new Date(2017,2,12));
}
This worked for me.:
var chart = AmCharts.makeChart('chartdiv', {
type: 'serial',
...
});
chart.addListener('dataUpdated', zoomChart);
zoomChart();
function zoomChart() {
chart.zoomToDates(new Date(2018, 2, 26), new Date(2018, 2, 28));
}
Note: The months parameter in Date() constructor are zero-based. January is 0, February is 1 and etc.
This is a continuation of an earlier thread, that was answered by Neil- thanks Neil! I probably should have included this in the first question, but I wanted to simplify things...
Another feature that I need is to have a "dialogue box" that holds a title and some text that animates on and off next to the circle when it comes on. I achieved this in my earlier version prior to the help from Neil. I have spent some time trying to integrate it into the new and improved code and get some unexpected results. For example, if I rollover the first circle on the right, it works as it should, however, if I try to rollover the middle and right circles, they don't work. Oddly, if I refresh and start on the right circle, each will work when moving right to left, until I reach the left one, and then the middle and right don't work- but the left one continues to work. Additionally, if I click on the left circle, it works as it should, but then the others don't work. And conversely, if I click on the right one first, and then move to the middle, the middle works on click, but then the right one does not.
The behavior that I am looking for is that each circle, animate up with the rectangle next to the circle fading in with dynamic text on mouseover and animate down, with the rectangle with text fading out on mouseout. The rectangle with text should fade out on click and not fade up again if the user mousesover the clicked circle (need to remove the mouseover event as well I guess). One additional thing that needs to happen is that the rectangle needs to appear in a different place on the circle, depending where on the map it is- so that it doesn't fall off the map. I did this successfully in the earlier version, but have left that out on the previous post for better clarity. I will include it here so you get the gist of what I'm doing.
My guess is that I need to create a set() of the rectangle/text component and place it in an another set() along with the circle?
Any help on this is truly appreciated! Thanks
// JavaScript Document
var arr = [
[50, 50, "this", "Is text that is to be the abstract"],
[100, 50, "that", "Is text that I hope is here"],
[150, 50, "another thing", "Even more text"]
];
var currRect;
var currTitleTxt;
var currTeaseTxt;
var prevItem;
doMe();
function doMe() {
var paper = new Raphael(document.getElementById('canvas_container'), 696, 348);
for (var i = 0; i < arr.length; i++) {
paper.circle(arr[i][0], arr[i][1], 6).attr({
fill: '#fff',
'fill-opacity': 0.5
}).data("i", [arr[i][0], arr[i][1], arr[i][2], arr[i][3]]).click(function () {
this.unmouseout();
}).click(function () {
if (this.data('selected') != 'true') {
if (prevItem != null) {
prevItem.data('selected', '');
handleOutState(prevItem);
}
prevItem = this.data('selected', 'true');
currRect.animate({"fill-opacity":0, "stroke-opacity":0}, 150 );
currTitleTxt.animate({"fill-opacity":0}, 150 );
currTeaseTxt.animate({"fill-opacity":0}, 150 );
}
}).mouseover(function () {
handleOverState(this);
if(this.data("i")[0] <= 350){ //this is where I test for the position on the map
paper.setStart(); //create rectangle and text set
currRect =paper.rect(17, -20, 265,90).attr({fill:"#999","fill-opacity":0.5});
currTitleTxt = paper.text(25, -8, this.data("i")[2]).attr({"text-anchor":"start",fill:'#ffffff',"font-size": 14, "font-weight":"bold","fill-opacity":0});
currTeaseTxt = paper.text(25, 30).attr({"text-anchor":"start",fill:'#eeeeee',"font-size": 11, "font-weight":"bold","fill-opacity":0});
var maxWidth = 250;
var content = this.data("i")[3];
var words = content.split(" ");
var tempText = ""; //since Raphael doesn't have native word wrap, I break the line manually
for (var i=0; i<words.length; i++) {
currTeaseTxt.attr("text", tempText + " " + words[i]);
if (currTeaseTxt.getBBox().width > maxWidth) {
tempText += "\n" + words[i];
} else {
tempText += " " + words[i];
}
}
currTeaseTxt.attr("text", tempText.substring(1));
var st = paper.setFinish();
st.translate(this.data("i")[0]+10, this.data("i")[1]+0).animate({"fill-opacity":1}, 150 );
}else if(this.data("i")[0] >= 351){ //this is where I test for the position on the map
paper.setStart();
currRect = paper.rect(-280, -20, 250,50).attr({fill:"#999","fill-opacity":0.5});
currTitleTxt = paper.text(-270, -10, this.data("i")[2]).attr({"text-anchor":"start",fill:'#ffffff',"font-size": 14, "font-weight":"bold","fill-opacity":0});
currTeaseTxt =paper.text(-270, 5, this.data("i")[3]).attr({"text-anchor":"start",fill:'#cccccc',"font-size": 12, "font-weight":"bold","fill-opacity":0});
var st = paper.setFinish();
st.translate(this.data("i")[0]+10, this.data("i")[1]+0).animate({"fill-opacity":1}, 150 );
}
}).mouseout(function () {
currRect.animate({"fill-opacity":0, "stroke-opacity":0}, 150 );
currTitleTxt.animate({"fill-opacity":0}, 150 );
currTeaseTxt.animate({"fill-opacity":0}, 150 );
if (this.data('selected') != 'true') handleOutState(this);
});
}
function handleOverState(el) {
el.animate({
r: 8
}, 250).animate({
"fill-opacity": 1
}, 150);
}
function handleOutState(el) {
el.animate({
r: 6
}, 250).animate({
"fill-opacity": 0.5
}, 150);
}
}
I am using the ASP.NET MVC 3.0 Chart Helper.
Fore some reason colors scheme (e.g Rainfall) applied only for Pie and Doughnut charts and not for any another types (Bar, Column etc).
The bars/columns on all another charts has all the same color. How to fix that?
Here is my chart:
chart = new System.Web.Helpers.Chart(width: 100, height: 200)
.AddSeries(
chartType: Bar,
legend: Rainfall
xValue: new[] { "Jan", "Feb", "Mar", "Apr", "May" },
yValues: new[] { "20", "20", "40", "10", "10" });
}
Also i was trying to use all schemes from System.Web.Helpers public static class ChartTheme, none of these helped
I found something that works ... its not great but it works
using the old Charting
using System.Web.UI.DataVisualization.Charting;
public ActionResult GetRainfallChart()
{
Chart chart = new Chart();
chart.BackColor = Color.Transparent;
chart.Width = Unit.Pixel(1400);
chart.Height = Unit.Pixel(750);
Series series1 = new Series("Series1");
series1.ChartArea = "ca1";
series1.ChartType = SeriesChartType.Bar;
series1.Font = new System.Drawing.Font("Verdana", 11f, FontStyle.Regular);
series1.Points.Add(new DataPoint
{
AxisLabel = "A",
YValues = new double[] { 100 },
Color = Color.Green,
});
series1.Points.Add(new DataPoint
{
AxisLabel = "B",
YValues = new double[] { 324 },
Color = Color.Red,
});
series1.Points.Add(new DataPoint
{
AxisLabel = "C",
YValues = new double[] { 235 },
Color = Color.Yellow,
});
chart.Series.Add(series1);
ChartArea ca1 = new ChartArea("ca1");
ca1.BackColor = Color.Transparent;
chart.ChartAreas.Add(ca1);
var ms = new MemoryStream();
chart.SaveImage(ms, ChartImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(ms, "image/png");
}
Yes he is correct. One more information
Chart Helper in System.Web.Helpers internally use 'using DV = System.Web.UI.DataVisualization.Charting;' ASP.Net chart control only but wrapped with limited access.
Its better you can use ASP.Net chart if you need more functions
Maybe you found the answer a long time ago but given the fact that I found very little on this topic when searching for a way to colorize the bars of a chart I thought it might be useful to post the solution here when it opened up to me.
It seems that the implementation of System.Web.Helpers.Chart is closely related to System.Web.UI.DataVisualization.Charting.Chart. Given this, I managed to find some clues as to how I could configure the "theme" XML properties:
public const String CHARTS_THEME = #"<Chart BackColor=""#EFEFEF"" BackGradientStyle=""TopBottom"" BorderColor=""#A0A0A0"" BorderWidth=""1"" Palette=""None"" PaletteCustomColors=""#ffcc00"" >
<ChartAreas>
<ChartArea Name=""Default"" _Template_=""All"" BackColor=""Transparent"" BackSecondaryColor=""White"" BorderWidth=""1"" BorderColor=""#A0A0A0"" BorderDashStyle=""Solid"" >
<AxisY>
<MajorGrid Interval=""Auto"" LineColor=""64, 64, 64, 64"" />
<LabelStyle Font=""Verdana, 10pt"" />
</AxisY>
<AxisX LineColor=""#000000"">
<MajorGrid Interval=""Auto"" LineColor=""64, 64, 64, 64"" />
<LabelStyle Font=""Verdana, 10pt"" />
</AxisX>
</ChartArea>
</ChartAreas>
<Legends>
<Legend _Template_=""All"" BackColor=""Transparent"" Docking=""Bottom"" Font=""Verdana, 10pt, style=Plain"" LegendStyle=""Row"">
</Legend>
</Legends>
</Chart>";
Key to this point is to define your own PaletteCustomColors (I have only one color). To make this work, the Palette property must be set to None.
Finally, just use your theme when creating an instance of your chart:
Chart chart = new Chart(width: 600, height: 200, theme:CHARTS_THEME);
Also check out the msdn documentation of System.Web.UI.DataVisualization.Charting.Chart to discover other ways to style your chart:
http://msdn.microsoft.com/en-us/library/dd467201.aspx